mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-06 14:50:20 +00:00
Compare commits
1366 Commits
3.2.0-b2
...
pratik/ote
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf116c4435 | ||
|
|
dcfdb60e9f | ||
|
|
2a4c174ad4 | ||
|
|
34a579e744 | ||
|
|
5709b7484e | ||
|
|
90b3726ed7 | ||
|
|
58973bc18b | ||
|
|
61649a3fab | ||
|
|
b30e11b6c1 | ||
|
|
e1ef515671 | ||
|
|
be50ecda06 | ||
|
|
f600660364 | ||
|
|
5cf22a9758 | ||
|
|
8c067f5fb4 | ||
|
|
4e21aefe74 | ||
|
|
bb0df16c90 | ||
|
|
5031ab2b4e | ||
|
|
90584be746 | ||
|
|
80d33662e4 | ||
|
|
1f4d03fae8 | ||
|
|
9631075634 | ||
|
|
d82f76d517 | ||
|
|
8c02c1595d | ||
|
|
a6a0de554b | ||
|
|
629514dcc3 | ||
|
|
ab849722e5 | ||
|
|
3b84e32941 | ||
|
|
6749e7831c | ||
|
|
52ec7f2a11 | ||
|
|
14a689f1ed | ||
|
|
2f027551c9 | ||
|
|
dbd2c7ad1b | ||
|
|
21bcedfd4a | ||
|
|
83f8fb7063 | ||
|
|
85ce6467d0 | ||
|
|
1ea7bb5252 | ||
|
|
25868f2740 | ||
|
|
2c590a47c5 | ||
|
|
d5d9d1d8da | ||
|
|
3c4e67b33f | ||
|
|
6ff90a2892 | ||
|
|
783644fb7d | ||
|
|
1839fa15e6 | ||
|
|
78fb08ef38 | ||
|
|
797d9449f3 | ||
|
|
ca5637e1d2 | ||
|
|
324b58d38f | ||
|
|
3abf7086a3 | ||
|
|
9f8a4c9561 | ||
|
|
f6cbc8d4e7 | ||
|
|
628726b51b | ||
|
|
94791ef924 | ||
|
|
2a90b582ff | ||
|
|
6961727ba7 | ||
|
|
0f43675440 | ||
|
|
36e4056869 | ||
|
|
adb4c7cdb4 | ||
|
|
cc3cb31cd9 | ||
|
|
10499f9f1a | ||
|
|
42abeb70a7 | ||
|
|
958e379e0a | ||
|
|
e86c1adc5d | ||
|
|
b0d46af0ca | ||
|
|
50377df271 | ||
|
|
7befb16705 | ||
|
|
9a4a97cdec | ||
|
|
6f47a7ab77 | ||
|
|
55712c1388 | ||
|
|
b0197105ea | ||
|
|
400f84aafe | ||
|
|
b59b3c1bb4 | ||
|
|
b5d36a80a8 | ||
|
|
40c2c7ece7 | ||
|
|
1a279e3edc | ||
|
|
5ade9431ef | ||
|
|
c669c20bc0 | ||
|
|
3cdb94aaf4 | ||
|
|
754894c6a0 | ||
|
|
4e566949da | ||
|
|
93f776fda4 | ||
|
|
84eb08eb8c | ||
|
|
fdfa651006 | ||
|
|
f1f0189c11 | ||
|
|
1ed08d09d0 | ||
|
|
8e4ab5bd04 | ||
|
|
73edc2dd58 | ||
|
|
a2e8f3a6b2 | ||
|
|
b724dd1e94 | ||
|
|
6e9d71c5eb | ||
|
|
b5faf37030 | ||
|
|
5c9bcfc43d | ||
|
|
f81919af05 | ||
|
|
4c46d39955 | ||
|
|
d2c7a00584 | ||
|
|
9e9bce5cd9 | ||
|
|
50379efdc1 | ||
|
|
55404b7011 | ||
|
|
8f14b40fbe | ||
|
|
20179dc0ed | ||
|
|
294b167355 | ||
|
|
f55f8c59fc | ||
|
|
3c0ac8f257 | ||
|
|
3d4119a3ba | ||
|
|
50c7d672d4 | ||
|
|
04df7fd92c | ||
|
|
d79a41dde6 | ||
|
|
78f3595bc1 | ||
|
|
55e136f1b4 | ||
|
|
389adbce62 | ||
|
|
ebc355a465 | ||
|
|
ffafa801f6 | ||
|
|
266b8db897 | ||
|
|
ddd5004dab | ||
|
|
7f8e8d1a4c | ||
|
|
7f1f3af43f | ||
|
|
f7ddfbe3a9 | ||
|
|
0fa2cc0c63 | ||
|
|
bf808399a2 | ||
|
|
074fb0ff22 | ||
|
|
c16b71dac6 | ||
|
|
73e386eca5 | ||
|
|
b8a5361e28 | ||
|
|
5cc837289a | ||
|
|
498bc3ee96 | ||
|
|
6e71ee1888 | ||
|
|
620396497f | ||
|
|
770092a405 | ||
|
|
cc37bc3775 | ||
|
|
c7d0fdfac3 | ||
|
|
58b02f3fbc | ||
|
|
293799ed31 | ||
|
|
3c9179e59d | ||
|
|
b9692c5536 | ||
|
|
4e60190199 | ||
|
|
3635fe0f2e | ||
|
|
508549f657 | ||
|
|
43eddb79f6 | ||
|
|
cb51e3f012 | ||
|
|
2668e9f8ca | ||
|
|
0ce13ff2e7 | ||
|
|
394a64f437 | ||
|
|
3a8f4fe729 | ||
|
|
ea2e6787c6 | ||
|
|
b3c5f8860c | ||
|
|
f48b212a1c | ||
|
|
0f1bd86ba2 | ||
|
|
6c21b92d15 | ||
|
|
cf629e2a76 | ||
|
|
190b9470a4 | ||
|
|
1da2596eea | ||
|
|
fa46aa55fc | ||
|
|
dc3d5107fd | ||
|
|
3cfec0f1e0 | ||
|
|
ce7fe95715 | ||
|
|
3463a85943 | ||
|
|
bc7cff230c | ||
|
|
54d92ff973 | ||
|
|
9a2d682a42 | ||
|
|
9d4570f65c | ||
|
|
ed0bab7c40 | ||
|
|
49103d635c | ||
|
|
4d5a71d327 | ||
|
|
50fdb38ace | ||
|
|
bb2ab4243b | ||
|
|
2ab43b6fda | ||
|
|
12a5d9014e | ||
|
|
b9eee1d245 | ||
|
|
0711a7b493 | ||
|
|
07c64f07f0 | ||
|
|
3097c157b6 | ||
|
|
556d62a0de | ||
|
|
eef8f4a4ff | ||
|
|
4fec58251b | ||
|
|
8bbbc2051e | ||
|
|
6736ab39df | ||
|
|
b68e1f7170 | ||
|
|
bb7c4d1c9f | ||
|
|
69d289a388 | ||
|
|
6341e75200 | ||
|
|
5a2c82f699 | ||
|
|
0b22050b5e | ||
|
|
ff02269c0d | ||
|
|
dd7401fde2 | ||
|
|
19a9ed7767 | ||
|
|
93eab33dc2 | ||
|
|
997267f845 | ||
|
|
e29b523620 | ||
|
|
b1f794f067 | ||
|
|
b6a1ad5bb3 | ||
|
|
772ea80a25 | ||
|
|
480676d0bf | ||
|
|
f07de6c454 | ||
|
|
cb2642be05 | ||
|
|
7e0ff536f5 | ||
|
|
044ca7719d | ||
|
|
cccce1c32e | ||
|
|
5de434436e | ||
|
|
45ddc1d868 | ||
|
|
7b9d55326d | ||
|
|
0364e4dc41 | ||
|
|
3c43f4614f | ||
|
|
6b63f0ff61 | ||
|
|
0ac8e6cf1e | ||
|
|
ed5f13481a | ||
|
|
781ef175c9 | ||
|
|
e5785c4fcb | ||
|
|
96d0563ea6 | ||
|
|
61dae6f792 | ||
|
|
fded06652a | ||
|
|
e833e8884d | ||
|
|
8e3eabc398 | ||
|
|
47b06ecd17 | ||
|
|
5a25c9188b | ||
|
|
82ee5b7556 | ||
|
|
f98c251011 | ||
|
|
e29dc474b3 | ||
|
|
2728e11809 | ||
|
|
9650fe8a6e | ||
|
|
58d217599b | ||
|
|
039d6ccf4f | ||
|
|
f9092e7948 | ||
|
|
2701ca416f | ||
|
|
2df96b1550 | ||
|
|
fe4c8ae82a | ||
|
|
f0b834a913 | ||
|
|
0e9a71798c | ||
|
|
ba85aecf00 | ||
|
|
37a4c9deb8 | ||
|
|
b34aa84e5a | ||
|
|
f5985e73ec | ||
|
|
4387aac1a5 | ||
|
|
482e136d19 | ||
|
|
0ff947454c | ||
|
|
b9f0a3ae19 | ||
|
|
34f41ea37f | ||
|
|
46dbc92b5f | ||
|
|
a84a700465 | ||
|
|
3e375d1034 | ||
|
|
7b7b3e7dcd | ||
|
|
f08c098049 | ||
|
|
bef40b37e7 | ||
|
|
2c3de0a445 | ||
|
|
8eac1a0e20 | ||
|
|
f76f0c00ac | ||
|
|
03b92bfca7 | ||
|
|
c30123796e | ||
|
|
88d2ff61cf | ||
|
|
a7c7e2767e | ||
|
|
5a6a00c458 | ||
|
|
a3c629251a | ||
|
|
f29f32816b | ||
|
|
c8c540e388 | ||
|
|
22bb4a74a9 | ||
|
|
663ef989eb | ||
|
|
6baad644b7 | ||
|
|
a0b5f0ecd7 | ||
|
|
e1045a84aa | ||
|
|
25447e508e | ||
|
|
1ad67e8aa5 | ||
|
|
84089b4b4d | ||
|
|
e5b46e6cf8 | ||
|
|
c8e5670aed | ||
|
|
43a70551b9 | ||
|
|
01dae36fb1 | ||
|
|
725b4fb5cb | ||
|
|
ae114c5341 | ||
|
|
19a6c2a306 | ||
|
|
172dff77d5 | ||
|
|
c3ccde3e39 | ||
|
|
282aec4367 | ||
|
|
8172c0c811 | ||
|
|
3cf7eea230 | ||
|
|
c13aac48c4 | ||
|
|
94a85627f2 | ||
|
|
e97878c5d7 | ||
|
|
bf7fcf58f0 | ||
|
|
a8a88410f4 | ||
|
|
5e7b71d600 | ||
|
|
68173a8354 | ||
|
|
2644179a42 | ||
|
|
6c62bfd2ad | ||
|
|
25227141b3 | ||
|
|
df395d6851 | ||
|
|
fb81d13d64 | ||
|
|
bb4f129aec | ||
|
|
5d116ff2ec | ||
|
|
4dff30b6c6 | ||
|
|
2709c7ebc5 | ||
|
|
686c8a8ffb | ||
|
|
be9cc0df5b | ||
|
|
3236daf08e | ||
|
|
8e618d68cd | ||
|
|
8f90b6a3d0 | ||
|
|
c28e432cd7 | ||
|
|
3272f07058 | ||
|
|
b1ecb718ba | ||
|
|
d058e5ac3c | ||
|
|
ae80391da6 | ||
|
|
6ec60ff52c | ||
|
|
2efc932165 | ||
|
|
8b2695b015 | ||
|
|
65e64205ef | ||
|
|
1b6945317e | ||
|
|
8add336c1a | ||
|
|
d27d67dfe4 | ||
|
|
59030e5d61 | ||
|
|
6c4c3e1049 | ||
|
|
5705c4dc02 | ||
|
|
448aaa4718 | ||
|
|
180c905a8a | ||
|
|
85103e4550 | ||
|
|
087fcf54b1 | ||
|
|
294c276e29 | ||
|
|
7cb08307a7 | ||
|
|
b1e6d90af1 | ||
|
|
9e6c5b5778 | ||
|
|
2f02fc2c04 | ||
|
|
4086ac9518 | ||
|
|
4d044e6254 | ||
|
|
d8d6142fbe | ||
|
|
afe0818c33 | ||
|
|
ca7282479f | ||
|
|
134a24d5bc | ||
|
|
480b6cab3c | ||
|
|
d5efd657ae | ||
|
|
d6450631bf | ||
|
|
e9cb9421ef | ||
|
|
cee157485e | ||
|
|
c0272f1314 | ||
|
|
ede8a53a76 | ||
|
|
fdcbf37e0b | ||
|
|
09c36d066e | ||
|
|
2f6b466feb | ||
|
|
8000adfa79 | ||
|
|
1f359f719c | ||
|
|
53ebd7a8c2 | ||
|
|
6c1d4d92bb | ||
|
|
ffc197b914 | ||
|
|
4a0994209e | ||
|
|
f37589b1f5 | ||
|
|
4ed409f35a | ||
|
|
d126868a25 | ||
|
|
56f2e2c4cf | ||
|
|
34d7280df4 | ||
|
|
dd0b6754d4 | ||
|
|
8908036b11 | ||
|
|
e205d0ef8e | ||
|
|
83cc5df72e | ||
|
|
97ca7d57bc | ||
|
|
8a4bf2dee6 | ||
|
|
38fbab1d18 | ||
|
|
c128625857 | ||
|
|
e11bf35691 | ||
|
|
d259ae34e3 | ||
|
|
bb48f1059e | ||
|
|
7784fa29fa | ||
|
|
93aae45c77 | ||
|
|
c35874be6d | ||
|
|
c2a39bc21f | ||
|
|
0ff2bed63a | ||
|
|
331d9d55b1 | ||
|
|
4ce882965a | ||
|
|
385c3cd91c | ||
|
|
7a4baab7cb | ||
|
|
3cb9a2bf51 | ||
|
|
81919612f8 | ||
|
|
81beeb6747 | ||
|
|
21c177c8f9 | ||
|
|
a7c3cd8a79 | ||
|
|
734cec319a | ||
|
|
ad48c7b3a9 | ||
|
|
9268242040 | ||
|
|
b32db4ceeb | ||
|
|
207864a98b | ||
|
|
742aa0878b | ||
|
|
57d382ceda | ||
|
|
848cbcbfbe | ||
|
|
4f53291fe8 | ||
|
|
0d302a897c | ||
|
|
a7ada14010 | ||
|
|
55a674f042 | ||
|
|
2569cd129a | ||
|
|
8617eaeb26 | ||
|
|
bf7f705de2 | ||
|
|
f3fc85fd2b | ||
|
|
0b36443382 | ||
|
|
91159c318b | ||
|
|
72b449c3ce | ||
|
|
ffd972f8be | ||
|
|
5e6817df61 | ||
|
|
b9de9d0abe | ||
|
|
f12c896ac7 | ||
|
|
8fe3f06999 | ||
|
|
d3c09fd3f4 | ||
|
|
142e8c5b36 | ||
|
|
e5f890e195 | ||
|
|
c712968890 | ||
|
|
2cbc3c139e | ||
|
|
fa71280795 | ||
|
|
fccb109e48 | ||
|
|
bb8f7f0e9a | ||
|
|
c43348886f | ||
|
|
5598b0eac7 | ||
|
|
0fb1aca461 | ||
|
|
0628dc172e | ||
|
|
010b0d1bd6 | ||
|
|
51003bc285 | ||
|
|
3e4b1eb60f | ||
|
|
3633ffc4c7 | ||
|
|
bbabe8d8a7 | ||
|
|
c552eb333f | ||
|
|
07599778d4 | ||
|
|
c9769d1add | ||
|
|
5e448e32f9 | ||
|
|
b602348e64 | ||
|
|
cf71dc0a01 | ||
|
|
9512930b0f | ||
|
|
be67ad25e7 | ||
|
|
3ee8f900ec | ||
|
|
a119efc478 | ||
|
|
a946ce7458 | ||
|
|
57a54ad0fe | ||
|
|
fe13359024 | ||
|
|
ea5991fb0a | ||
|
|
ee9fbc4e08 | ||
|
|
577d7457f1 | ||
|
|
f5f13df5ff | ||
|
|
4a3ba35c1d | ||
|
|
c5b96afe07 | ||
|
|
5cedc5d44b | ||
|
|
f12fa8d3a9 | ||
|
|
a389f922dd | ||
|
|
899fc3c912 | ||
|
|
79f4ddc4a6 | ||
|
|
3bb4ea84a4 | ||
|
|
3c1189d6f8 | ||
|
|
b79991b190 | ||
|
|
4c2125c07e | ||
|
|
4ea4b99f15 | ||
|
|
949887feb9 | ||
|
|
d83cb0bdb3 | ||
|
|
758a3fec29 | ||
|
|
2ee4d2ff2d | ||
|
|
a23d83f393 | ||
|
|
24f60ab1d4 | ||
|
|
22b533ac51 | ||
|
|
8046a30e9b | ||
|
|
4a8aa9e514 | ||
|
|
fd1c8c6060 | ||
|
|
5c275ac476 | ||
|
|
8dcd6f9b4a | ||
|
|
cfb2d87cab | ||
|
|
283218896b | ||
|
|
f7df1742fb | ||
|
|
dc5bb4b35c | ||
|
|
fc57dab78b | ||
|
|
63ffdc39dc | ||
|
|
cb9fce6890 | ||
|
|
a1c79b7aab | ||
|
|
0d1d1aa0e1 | ||
|
|
c97f29c0dd | ||
|
|
1fa39fdef6 | ||
|
|
fb9e6e5452 | ||
|
|
18121a8cf4 | ||
|
|
ea56a3a0d4 | ||
|
|
93c31573c5 | ||
|
|
6571f75d39 | ||
|
|
2111bb4b95 | ||
|
|
750e4dc5c6 | ||
|
|
ceea9e49dd | ||
|
|
bcc5ab66c4 | ||
|
|
db4d70bbc2 | ||
|
|
b8dd848899 | ||
|
|
b321792a14 | ||
|
|
72642b5dc6 | ||
|
|
db5b93e2c4 | ||
|
|
f37a4a1022 | ||
|
|
8f3974c094 | ||
|
|
283fbaa54f | ||
|
|
3167a49f41 | ||
|
|
759d3506b2 | ||
|
|
021300538a | ||
|
|
a71d6635e6 | ||
|
|
3df7e9cba6 | ||
|
|
6a16dfa823 | ||
|
|
6428c9f13c | ||
|
|
8abe82eefa | ||
|
|
5b8e6cd1dd | ||
|
|
d7e847a53b | ||
|
|
c3bdcb4291 | ||
|
|
478b58395b | ||
|
|
cf075888ff | ||
|
|
d6b314e8d5 | ||
|
|
5b53ac99be | ||
|
|
0a800069bf | ||
|
|
938a4d17ce | ||
|
|
ca3a78abce | ||
|
|
eef11a65fa | ||
|
|
9c40039fda | ||
|
|
7659de151c | ||
|
|
c20d10fd36 | ||
|
|
91ff486950 | ||
|
|
7d8e908879 | ||
|
|
6205199dc7 | ||
|
|
9376aa7c88 | ||
|
|
864ac729de | ||
|
|
793d2ecfce | ||
|
|
7a509a01eb | ||
|
|
d3955d3639 | ||
|
|
12e81abef3 | ||
|
|
d7baf262f8 | ||
|
|
b286335ccf | ||
|
|
5c2997d95e | ||
|
|
342b9f55a1 | ||
|
|
000ad1d1f5 | ||
|
|
17ffe8b049 | ||
|
|
63c6f3b8df | ||
|
|
4174aef07b | ||
|
|
e6643a4389 | ||
|
|
80800ee130 | ||
|
|
ebc5c5ed9d | ||
|
|
61c2760296 | ||
|
|
d9f668dbe4 | ||
|
|
88ac4b6aee | ||
|
|
a5f80514a9 | ||
|
|
90f7a8bd4e | ||
|
|
45ab508ed8 | ||
|
|
1ccc1bd286 | ||
|
|
a6cebf21b0 | ||
|
|
6c71aa8c2a | ||
|
|
9b46a343fc | ||
|
|
10b4112382 | ||
|
|
859bd21ca5 | ||
|
|
230706ef67 | ||
|
|
15d3e3a375 | ||
|
|
0fe09cda9b | ||
|
|
a9cc1067d0 | ||
|
|
194f5b8af8 | ||
|
|
37c9168065 | ||
|
|
878b91b4f7 | ||
|
|
373012e84d | ||
|
|
8f9fa52f93 | ||
|
|
fb7c3bc38d | ||
|
|
8e606bbaf4 | ||
|
|
aacb67f71b | ||
|
|
40fba327cf | ||
|
|
811b934004 | ||
|
|
c80038fd42 | ||
|
|
7397bbcdd2 | ||
|
|
4785aa9217 | ||
|
|
8259026a25 | ||
|
|
9947a52e79 | ||
|
|
ee2f1b4fbf | ||
|
|
2627ea7f65 | ||
|
|
6c543426c3 | ||
|
|
e5cf1a0985 | ||
|
|
023bdaeeed | ||
|
|
4e422a0354 | ||
|
|
36cae13352 | ||
|
|
cddb220221 | ||
|
|
56d33fc87f | ||
|
|
013252f210 | ||
|
|
970914d2ce | ||
|
|
289b049b70 | ||
|
|
dfd67b8124 | ||
|
|
85887f7292 | ||
|
|
a675897aaf | ||
|
|
f60c995fe1 | ||
|
|
fff8598a33 | ||
|
|
ac1805f0a4 | ||
|
|
0fec5272cb | ||
|
|
95d9335974 | ||
|
|
365907ab22 | ||
|
|
8b5ded4324 | ||
|
|
39f3b86d17 | ||
|
|
2ef026aef5 | ||
|
|
03fffec640 | ||
|
|
a13a858112 | ||
|
|
f6b4d945d8 | ||
|
|
96b2c0964f | ||
|
|
146ea1455b | ||
|
|
d6fe31442e | ||
|
|
a4bc7bd611 | ||
|
|
8adb5d03da | ||
|
|
66552e7858 | ||
|
|
2264a8427a | ||
|
|
c5bdaafc39 | ||
|
|
4b6c1c270f | ||
|
|
945355d6c6 | ||
|
|
b9704c9549 | ||
|
|
9c69aab326 | ||
|
|
3eeb8b3730 | ||
|
|
93c27997b4 | ||
|
|
ac79a5123e | ||
|
|
1b227a1eff | ||
|
|
b0e9e1a24d | ||
|
|
bf0b843ce1 | ||
|
|
fce770e4f4 | ||
|
|
8dd5ac55e8 | ||
|
|
507828edde | ||
|
|
aca6623f14 | ||
|
|
765c96919c | ||
|
|
7a9215a4d3 | ||
|
|
dd9cde88f3 | ||
|
|
e52f1470b6 | ||
|
|
1a2f9a71f5 | ||
|
|
ebf107e73c | ||
|
|
d5f9242f84 | ||
|
|
84fc829be3 | ||
|
|
bdd7dea4b4 | ||
|
|
b46ee12a19 | ||
|
|
1441d4690d | ||
|
|
225ed204ad | ||
|
|
ad111bcc22 | ||
|
|
860b1601c7 | ||
|
|
e07a0c347f | ||
|
|
25e08b1840 | ||
|
|
66e6310b56 | ||
|
|
11717a5431 | ||
|
|
994e425804 | ||
|
|
d4cb68d5a1 | ||
|
|
e804ec83aa | ||
|
|
615d339f84 | ||
|
|
ece8c62bca | ||
|
|
bed6770751 | ||
|
|
dfdda305ee | ||
|
|
2ac93c504e | ||
|
|
124e3a154d | ||
|
|
e209ee5371 | ||
|
|
109b649106 | ||
|
|
5697e43921 | ||
|
|
47f3480fb9 | ||
|
|
20a6274a48 | ||
|
|
7c09c38a5b | ||
|
|
c5ae16cdd9 | ||
|
|
1162b6f3bc | ||
|
|
0bcc7635ac | ||
|
|
1ab28d0cf0 | ||
|
|
967f0082c3 | ||
|
|
eeae09c645 | ||
|
|
34a2afa704 | ||
|
|
b8602b7821 | ||
|
|
7bc6c65bb2 | ||
|
|
d6fefe2468 | ||
|
|
781e08a6a6 | ||
|
|
a8d70c15f8 | ||
|
|
98fc939851 | ||
|
|
33aa48b2c7 | ||
|
|
4d6ddb5f1f | ||
|
|
dc7788aab8 | ||
|
|
cd6264c02f | ||
|
|
7aebc62223 | ||
|
|
6554f04252 | ||
|
|
f51b113f4b | ||
|
|
7cf55315b5 | ||
|
|
3cd3d5e80e | ||
|
|
c76008d24c | ||
|
|
1fd0ee5999 | ||
|
|
771eb3d66c | ||
|
|
b24456abb4 | ||
|
|
ded9847eaf | ||
|
|
9918803333 | ||
|
|
bf527a41dd | ||
|
|
154d441ff2 | ||
|
|
ce6a3153a1 | ||
|
|
3115313551 | ||
|
|
2e61a1c412 | ||
|
|
046e2e2b85 | ||
|
|
e901a3604a | ||
|
|
523bfdbbe1 | ||
|
|
9f81e770eb | ||
|
|
670b6ef3d5 | ||
|
|
5d1e3f207c | ||
|
|
0fffe23abc | ||
|
|
7e15621e7b | ||
|
|
99431d7833 | ||
|
|
47365f4220 | ||
|
|
1599c1a672 | ||
|
|
763dd503be | ||
|
|
e321f294e5 | ||
|
|
1a0780fd3e | ||
|
|
5dd5e765ae | ||
|
|
c157253372 | ||
|
|
280217653d | ||
|
|
ba7e1f98e4 | ||
|
|
d7579b2861 | ||
|
|
088848e7ab | ||
|
|
e7dea147cd | ||
|
|
8d730b8b9a | ||
|
|
e5fae351d6 | ||
|
|
a44d91ec27 | ||
|
|
2f96c6547c | ||
|
|
c187a62353 | ||
|
|
c848e51e13 | ||
|
|
8395e69e94 | ||
|
|
8f9057729c | ||
|
|
f031befc6e | ||
|
|
4e8d37facf | ||
|
|
071ad45d31 | ||
|
|
c9901595f7 | ||
|
|
3a1f22583f | ||
|
|
e1163f7180 | ||
|
|
f66a53cfc9 | ||
|
|
68a69d9064 | ||
|
|
309d3e6449 | ||
|
|
4e0b6f5b9e | ||
|
|
a35003b123 | ||
|
|
53e8c4d54e | ||
|
|
c070177800 | ||
|
|
5700eeed1b | ||
|
|
23387f6a06 | ||
|
|
d6b101069e | ||
|
|
c9521b97fe | ||
|
|
248d85cae6 | ||
|
|
7ac5343119 | ||
|
|
954223958f | ||
|
|
2f3558c610 | ||
|
|
c6c019ed8b | ||
|
|
43258e8dc0 | ||
|
|
9e89d74d2f | ||
|
|
8b790ebac9 | ||
|
|
4bd1176df5 | ||
|
|
4c4c6f5de2 | ||
|
|
9498b2865f | ||
|
|
f9551ac5ca | ||
|
|
1acc42313c | ||
|
|
396d772a15 | ||
|
|
64ffcffe32 | ||
|
|
bfdcd3da87 | ||
|
|
1438bf1c67 | ||
|
|
f6f0cb1a5f | ||
|
|
6aa8570d6c | ||
|
|
824f63216a | ||
|
|
a104140a51 | ||
|
|
7da643d864 | ||
|
|
ce04dac32e | ||
|
|
0330d037ef | ||
|
|
5c92ebefb2 | ||
|
|
1162371def | ||
|
|
2a0feca46b | ||
|
|
108a4c8217 | ||
|
|
4584b01bde | ||
|
|
7c59786565 | ||
|
|
9623e67b76 | ||
|
|
85af406a0f | ||
|
|
ac33fb32a7 | ||
|
|
23d0812827 | ||
|
|
49567e7283 | ||
|
|
633ef4706f | ||
|
|
49cb3f45a4 | ||
|
|
22a21b175e | ||
|
|
e9d885bd9b | ||
|
|
a911f9089e | ||
|
|
e34c2667d7 | ||
|
|
30de556224 | ||
|
|
dcd2ff0b5f | ||
|
|
dfb9b8ed9a | ||
|
|
179e73594a | ||
|
|
15dd653e4b | ||
|
|
a37afe13ff | ||
|
|
3547a9335f | ||
|
|
1a98182e23 | ||
|
|
79308705c5 | ||
|
|
e24de65f42 | ||
|
|
7fdaa0a5ef | ||
|
|
795dc5e364 | ||
|
|
f6fd5ddb0a | ||
|
|
afcf6fbcdc | ||
|
|
28cc20c816 | ||
|
|
a830ab10ef | ||
|
|
8c0080020f | ||
|
|
9cb0740673 | ||
|
|
242ce3e9e4 | ||
|
|
a5d238e7d4 | ||
|
|
9cb049276d | ||
|
|
93ac1aa7aa | ||
|
|
d9a3af8207 | ||
|
|
8d1083e5ea | ||
|
|
1e45d363c5 | ||
|
|
ad3d172a1f | ||
|
|
ad7232cbc5 | ||
|
|
93836f22db | ||
|
|
c7ecfc6a97 | ||
|
|
28befc672c | ||
|
|
6809690fad | ||
|
|
5b6e8b6f93 | ||
|
|
028f0cb5da | ||
|
|
15b3ed1ae7 | ||
|
|
cce4cfef10 | ||
|
|
fb8f792973 | ||
|
|
318400327c | ||
|
|
0e25103fdb | ||
|
|
3f8aa47224 | ||
|
|
ac57a91b77 | ||
|
|
2735e4ac78 | ||
|
|
145b1469d6 | ||
|
|
a9f52458b3 | ||
|
|
0e5e802e5e | ||
|
|
6985e1948b | ||
|
|
a844c14e49 | ||
|
|
c3c980e858 | ||
|
|
92bc0b24b8 | ||
|
|
1a36ef4b0f | ||
|
|
a789f6ccf5 | ||
|
|
44cdc8133e | ||
|
|
dfe91e071f | ||
|
|
dec8b0a9a1 | ||
|
|
df1d8aed44 | ||
|
|
41d72cb51b | ||
|
|
45e1c15d24 | ||
|
|
865ab65a07 | ||
|
|
009c63e7db | ||
|
|
5d70a5fffd | ||
|
|
f3a095ab65 | ||
|
|
34bf61ff77 | ||
|
|
9d99ce6ae8 | ||
|
|
577cb9b5f0 | ||
|
|
7d202127bb | ||
|
|
56090b0ead | ||
|
|
6c6d6f953f | ||
|
|
0b4b3c7bf2 | ||
|
|
3e894f8e93 | ||
|
|
cb7dc5c52e | ||
|
|
9cfb43d8d0 | ||
|
|
7ada57e2a8 | ||
|
|
53e1ff82d8 | ||
|
|
8df3ea1bbe | ||
|
|
5a6882f119 | ||
|
|
b449db0434 | ||
|
|
9babfff3c8 | ||
|
|
68b32ed0f0 | ||
|
|
61ab5c6fe3 | ||
|
|
837f7e7b50 | ||
|
|
b392035544 | ||
|
|
450004ebd8 | ||
|
|
6f403fdd1b | ||
|
|
5dc4ae8fcc | ||
|
|
690841e934 | ||
|
|
7d61a4a0ef | ||
|
|
93caaba5ca | ||
|
|
afbccf971a | ||
|
|
2f65cb5610 | ||
|
|
d4ebd6a168 | ||
|
|
02fe838257 | ||
|
|
20477e5494 | ||
|
|
551f3c3b96 | ||
|
|
f0c6227c06 | ||
|
|
a9e4006591 | ||
|
|
eac2538a55 | ||
|
|
ddca4a982b | ||
|
|
9fbdfa1fbe | ||
|
|
3c13d788fd | ||
|
|
aa5e4ff89f | ||
|
|
977e5a7dba | ||
|
|
c10b0fd9d1 | ||
|
|
3131d99029 | ||
|
|
829094df5a | ||
|
|
9554e3889b | ||
|
|
fe7cb33b65 | ||
|
|
f5cf4155c2 | ||
|
|
ea30adeb47 | ||
|
|
9bc2e4abb3 | ||
|
|
7b9e0bc300 | ||
|
|
a04459f1f8 | ||
|
|
6b5e6a49ec | ||
|
|
b4e4b57504 | ||
|
|
6dd43765b5 | ||
|
|
cbf389943f | ||
|
|
b05e650b6f | ||
|
|
57175ab12c | ||
|
|
d44a0aa3ff | ||
|
|
522fe562ff | ||
|
|
745102360b | ||
|
|
19d9c44cf5 | ||
|
|
5c14b57462 | ||
|
|
c875944e05 | ||
|
|
2430032e3a | ||
|
|
0f63d14999 | ||
|
|
faaec003f4 | ||
|
|
815e2b1f5d | ||
|
|
ec8e3e2950 | ||
|
|
495d5bd8a0 | ||
|
|
6cd910f06f | ||
|
|
5cd71ed107 | ||
|
|
9e27120a15 | ||
|
|
648ec747f2 | ||
|
|
e60efd4d2f | ||
|
|
c48f5ed6e7 | ||
|
|
c9fe4b1a14 | ||
|
|
46d1012ad4 | ||
|
|
7eeddd3ad9 | ||
|
|
e339ba1f6b | ||
|
|
ac1b01b4c7 | ||
|
|
497dd007d9 | ||
|
|
0d845149ec | ||
|
|
7a854ccad2 | ||
|
|
c8b42a7f48 | ||
|
|
592e546f82 | ||
|
|
201da0e00d | ||
|
|
5601615952 | ||
|
|
4ad94ae2ff | ||
|
|
580ee5ede7 | ||
|
|
937d11d7c3 | ||
|
|
411286c519 | ||
|
|
689395a705 | ||
|
|
4cbb1be5b4 | ||
|
|
e8bdbaa1e8 | ||
|
|
8e9e852b74 | ||
|
|
db04120f74 | ||
|
|
3c4d51a408 | ||
|
|
fac3287912 | ||
|
|
4470ae7bc9 | ||
|
|
25d2dae798 | ||
|
|
47e83c4e65 | ||
|
|
4bbe28cb92 | ||
|
|
782d98d249 | ||
|
|
93a7df6147 | ||
|
|
dd4911ef5e | ||
|
|
c096eeb239 | ||
|
|
6340c986c9 | ||
|
|
170eb5e588 | ||
|
|
590906dadf | ||
|
|
448ae8b9df | ||
|
|
45b1f4dbeb | ||
|
|
8012b5d34f | ||
|
|
6c2266c5c7 | ||
|
|
aa55392453 | ||
|
|
c4c95dbe76 | ||
|
|
a761b0d43c | ||
|
|
cdee9a675c | ||
|
|
779b49cd93 | ||
|
|
4f8142fd10 | ||
|
|
4a9f72c73e | ||
|
|
7afdd71a54 | ||
|
|
af89854a43 | ||
|
|
d6c4e6cb93 | ||
|
|
d67e06102a | ||
|
|
8c71ec803d | ||
|
|
8e2aa33f64 | ||
|
|
13b72a4120 | ||
|
|
e49c5997b7 | ||
|
|
5ad5bacc94 | ||
|
|
fcae50a487 | ||
|
|
98aa9c9095 | ||
|
|
92072ecca4 | ||
|
|
85330920ac | ||
|
|
a61cdf0214 | ||
|
|
fac6c3ac1d | ||
|
|
a8549a7ab2 | ||
|
|
761688383d | ||
|
|
ed31bab500 | ||
|
|
b62987bda7 | ||
|
|
a4720d0449 | ||
|
|
f08412b3e0 | ||
|
|
50244a8637 | ||
|
|
5e1c35f7f7 | ||
|
|
27f7fdb3a6 | ||
|
|
6e6fb9cdf3 | ||
|
|
e092c52409 | ||
|
|
d050073842 | ||
|
|
8490206228 | ||
|
|
8995564ed6 | ||
|
|
182d844996 | ||
|
|
37b895b678 | ||
|
|
a4fc3c878e | ||
|
|
82696f6f8e | ||
|
|
43ae5c2d20 | ||
|
|
8dec56df0b | ||
|
|
beaf01ae4d | ||
|
|
bf390559f5 | ||
|
|
40cc7f9ed7 | ||
|
|
5e37f71139 | ||
|
|
3f793b2098 | ||
|
|
f4555c80fe | ||
|
|
b4f9f47295 | ||
|
|
f2bc0b18f2 | ||
|
|
5bdb6b4eaf | ||
|
|
f44b89b99d | ||
|
|
c6053f5d64 | ||
|
|
31180f94c2 | ||
|
|
6407f0fa52 | ||
|
|
4d0ea8ae36 | ||
|
|
db4c3788cb | ||
|
|
58a3be4517 | ||
|
|
1c0d21dba4 | ||
|
|
f183e9b57f | ||
|
|
a0477f9475 | ||
|
|
1658d3dc40 | ||
|
|
8e7a2d6c53 | ||
|
|
9adcc49171 | ||
|
|
8e44c95d6a | ||
|
|
b659d43395 | ||
|
|
70d86d7ebf | ||
|
|
9e12e660fe | ||
|
|
7ab6f4d34b | ||
|
|
81b47afde7 | ||
|
|
b65f91117f | ||
|
|
57ed0d9fd0 | ||
|
|
51918ef868 | ||
|
|
c8674d61b8 | ||
|
|
cf18032e7f | ||
|
|
f18ddd95c1 | ||
|
|
769668579a | ||
|
|
dbd646bd53 | ||
|
|
6ae090ba45 | ||
|
|
7be98d95de | ||
|
|
e6266e4e8d | ||
|
|
025620cc4e | ||
|
|
692ce65f3e | ||
|
|
bb3732c22f | ||
|
|
87a8780b73 | ||
|
|
78522ba18e | ||
|
|
79fbb9c303 | ||
|
|
e21e7b0d51 | ||
|
|
3dd2f34591 | ||
|
|
521e0756e1 | ||
|
|
dbcd040180 | ||
|
|
17e69e660c | ||
|
|
ef10c754b1 | ||
|
|
912890c104 | ||
|
|
ac68091bec | ||
|
|
2773de7b54 | ||
|
|
5f7de1bb48 | ||
|
|
264516c37d | ||
|
|
eb84ac57c7 | ||
|
|
021c81e978 | ||
|
|
ab6b6d215e | ||
|
|
53d0daf3b4 | ||
|
|
8fb33b0818 | ||
|
|
61cb1faf8f | ||
|
|
5cbb349efa | ||
|
|
93bed03d8d | ||
|
|
581ab8f552 | ||
|
|
6154357daa | ||
|
|
3a1e462bef | ||
|
|
30af98200f | ||
|
|
ff27e62e1f | ||
|
|
7e93e75d8e | ||
|
|
ecd02134fa | ||
|
|
3c0eec0209 | ||
|
|
7b9e2cf91f | ||
|
|
312dec2baa | ||
|
|
be812b8d21 | ||
|
|
e63a5f72be | ||
|
|
1e2287e6e1 | ||
|
|
3508917f17 | ||
|
|
3ed22580fe | ||
|
|
f434706eec | ||
|
|
8a54ef1600 | ||
|
|
612a32d047 | ||
|
|
7be06aaae0 | ||
|
|
70aa2b66dd | ||
|
|
887b35821d | ||
|
|
faf9342695 | ||
|
|
0a371dca7d | ||
|
|
6c904a5593 | ||
|
|
75191e472b | ||
|
|
1e6d55bbce | ||
|
|
86ef6ff2cf | ||
|
|
6157624103 | ||
|
|
54c97daaf1 | ||
|
|
654fe2d30f | ||
|
|
0012f52940 | ||
|
|
46af5bdc5a | ||
|
|
a05ada89ec | ||
|
|
8afe604aff | ||
|
|
417d7ec6d5 | ||
|
|
2918001602 | ||
|
|
6a8053df2d | ||
|
|
e2a7802945 | ||
|
|
7f0a8a7ed7 | ||
|
|
39f690a751 | ||
|
|
d6ee6c6bbc | ||
|
|
4d3d15eda8 | ||
|
|
8bed4bc95a | ||
|
|
92072d0304 | ||
|
|
94005ca0e4 | ||
|
|
780cc434a7 | ||
|
|
20fabbc0ec | ||
|
|
f7275b7ad9 | ||
|
|
39273e3aae | ||
|
|
9f571e5d1e | ||
|
|
dc3cfc325c | ||
|
|
ac11217195 | ||
|
|
103dd605d2 | ||
|
|
12b7316f71 | ||
|
|
b933e8ae00 | ||
|
|
a1cb752745 | ||
|
|
fb04271204 | ||
|
|
35fb33438f | ||
|
|
36c4363c54 | ||
|
|
831be14fd9 | ||
|
|
019e84f0d2 | ||
|
|
0dec657c61 | ||
|
|
694abe2004 | ||
|
|
b7c9e5775e | ||
|
|
2aa8dbc2cb | ||
|
|
8daf09b3ce | ||
|
|
a3044bcef9 | ||
|
|
3433c9583d | ||
|
|
a271744d42 | ||
|
|
09c5f5c3bf | ||
|
|
b8d3c52017 | ||
|
|
21dad9a17d | ||
|
|
e07391fe78 | ||
|
|
9515177e29 | ||
|
|
1a96f75954 | ||
|
|
88e25119f0 | ||
|
|
c5a59645d9 | ||
|
|
c0a5f57cdf | ||
|
|
8e97c7329a | ||
|
|
fe058d49b4 | ||
|
|
bd6e58a20e | ||
|
|
e8c826c816 | ||
|
|
d753191d20 | ||
|
|
d4e91b462e | ||
|
|
c01f8ae99c | ||
|
|
fb25d97077 | ||
|
|
d50e0ff48e | ||
|
|
d990f7f197 | ||
|
|
1e4ce19556 | ||
|
|
bc49eb6f83 | ||
|
|
90c2321bb8 | ||
|
|
46b997b774 | ||
|
|
901b3e34f6 | ||
|
|
908eb841bd | ||
|
|
128de625e2 | ||
|
|
ebd84a2338 | ||
|
|
fa2736277f | ||
|
|
196c309d1d | ||
|
|
d46d015fd5 | ||
|
|
999bf83f15 | ||
|
|
96470e0c8d | ||
|
|
cb7ee2358d | ||
|
|
b54b17708f | ||
|
|
cbbd6ebee2 | ||
|
|
de7194011d | ||
|
|
ae475793d5 | ||
|
|
f6105ece98 | ||
|
|
360698d79d | ||
|
|
b136b80c13 | ||
|
|
7e47c6303f | ||
|
|
689e803cc7 | ||
|
|
34ee231d62 | ||
|
|
4f4b4dd199 | ||
|
|
d87839230a | ||
|
|
e2cb811bf7 | ||
|
|
2bb0995ff8 | ||
|
|
793fe65a96 | ||
|
|
737b0f5488 | ||
|
|
ded848075d | ||
|
|
397c66cede | ||
|
|
2fb165cd54 | ||
|
|
c585d9b66c | ||
|
|
79ed703bb2 | ||
|
|
441c88dfb1 | ||
|
|
178bc916a8 | ||
|
|
19eead6955 | ||
|
|
ed8164d502 | ||
|
|
682d7a8d76 | ||
|
|
eb51457e69 | ||
|
|
65817c4c57 | ||
|
|
9bc8cc6b4e | ||
|
|
832648c351 | ||
|
|
21b58a8885 | ||
|
|
a9ee819ea1 | ||
|
|
736579e473 | ||
|
|
3b93e2d4d9 | ||
|
|
ac9bd2c055 | ||
|
|
4124762343 | ||
|
|
ea8600e204 | ||
|
|
895e9167b0 | ||
|
|
d15d2d2df6 | ||
|
|
75bcd4ff53 | ||
|
|
a73117ddd0 | ||
|
|
9e4d943c69 | ||
|
|
025a8a344b | ||
|
|
9ee9e566d4 | ||
|
|
0de807b1be | ||
|
|
59ee027d8a | ||
|
|
7aa4486741 | ||
|
|
5e8277f36a | ||
|
|
573593ae31 | ||
|
|
a5c405f4be | ||
|
|
e9c5c3520e | ||
|
|
26947267b1 | ||
|
|
4bb2030315 | ||
|
|
3852b5ae4b | ||
|
|
ea921d3a02 | ||
|
|
ca2d616277 | ||
|
|
88686af850 | ||
|
|
7f4ef83df0 | ||
|
|
1fd971b78b | ||
|
|
747247153b | ||
|
|
147da57348 | ||
|
|
d6c8dec451 | ||
|
|
3547112540 | ||
|
|
30ecb32a6f | ||
|
|
dc13e9d680 | ||
|
|
f11ebc1253 | ||
|
|
577d1f8a21 | ||
|
|
df79d5e74b | ||
|
|
4dc923dcc5 | ||
|
|
158df5394c | ||
|
|
a6bd9251d2 | ||
|
|
9ae29612ea | ||
|
|
82abf2a849 | ||
|
|
7cfa5d4610 | ||
|
|
248cb29681 | ||
|
|
7a449edebb | ||
|
|
19da25812b | ||
|
|
7cd503859e | ||
|
|
b41cbb08c6 | ||
|
|
bd1b126230 | ||
|
|
1c6cdc653c | ||
|
|
4ab20770f7 | ||
|
|
2e307329f0 | ||
|
|
3429845c40 | ||
|
|
7c7c1894b9 | ||
|
|
45d4aacb53 | ||
|
|
ce3951bbb3 | ||
|
|
ab887f5049 | ||
|
|
ea023121f5 | ||
|
|
4b198cd5bb | ||
|
|
726f20c8f6 | ||
|
|
96643bb0fa | ||
|
|
e83818241a | ||
|
|
a01b274352 | ||
|
|
852fbe955d | ||
|
|
b33d0a0479 | ||
|
|
653a383ff5 | ||
|
|
affe5835fe | ||
|
|
ef2642f873 | ||
|
|
b2038163bc | ||
|
|
f1a5ba43ad | ||
|
|
4a73be499d | ||
|
|
193f5b39cb | ||
|
|
db8111ef7c | ||
|
|
913a4b794c | ||
|
|
accea17e9d | ||
|
|
c6fa00fbe3 | ||
|
|
bfb8f4f01a | ||
|
|
4b745a86b7 | ||
|
|
ddf894dcb0 | ||
|
|
d52d735543 | ||
|
|
6a0ce46755 | ||
|
|
2f029a2120 | ||
|
|
61fbde3a71 | ||
|
|
e2e537b3bb | ||
|
|
a873250019 | ||
|
|
56c9d1d497 | ||
|
|
d52dd29d20 | ||
|
|
7793b5f10b | ||
|
|
dfcad69155 | ||
|
|
6d1a5be8d2 | ||
|
|
b0fe2ec58a | ||
|
|
c00ed673a8 | ||
|
|
f239256d87 | ||
|
|
00761dbb67 | ||
|
|
077e03ff33 | ||
|
|
7d524a03b8 | ||
|
|
c0ee813666 | ||
|
|
8e05416211 | ||
|
|
81555d5456 | ||
|
|
6b55c4cdc8 | ||
|
|
3414a1776b | ||
|
|
6d9ed125f3 | ||
|
|
02fa55df8d | ||
|
|
6e2452207d | ||
|
|
29e49abd3c | ||
|
|
ae21f53e4d | ||
|
|
bee1056faa | ||
|
|
b6aa4a8fde | ||
|
|
8583343fd9 | ||
|
|
7e149f7773 | ||
|
|
a142a700e8 | ||
|
|
a0eeb8eb9e | ||
|
|
f4d327fda7 | ||
|
|
ff1502f939 | ||
|
|
ecf103104b | ||
|
|
e1f30c1a22 | ||
|
|
e63ca4c495 | ||
|
|
711ae43174 | ||
|
|
898d05de66 | ||
|
|
5de8c520d1 | ||
|
|
0644438549 | ||
|
|
d8c586b2fb | ||
|
|
8cca4ec77b | ||
|
|
38fca631cd | ||
|
|
5f139e12c3 | ||
|
|
1defb2111f | ||
|
|
350e398aa6 | ||
|
|
92607805c3 | ||
|
|
45ffe8e2ec | ||
|
|
b0e0d5930a | ||
|
|
50e6b14c56 | ||
|
|
b92354715d | ||
|
|
81298ceb9f | ||
|
|
936c73982d | ||
|
|
d426f4983a | ||
|
|
892fee638a | ||
|
|
facc111c22 | ||
|
|
5ec9f3f30a | ||
|
|
8f364ed6f4 | ||
|
|
30c430aec8 | ||
|
|
fdec3ce5c4 | ||
|
|
aa062ecdbe | ||
|
|
0e15f95543 | ||
|
|
eca887c66e | ||
|
|
f51976f63e | ||
|
|
1f2a36b316 | ||
|
|
8365f7dda3 | ||
|
|
391b8f91ce | ||
|
|
2f7064ace6 | ||
|
|
1ef234de9d | ||
|
|
a37cf74868 | ||
|
|
21192e9b3f | ||
|
|
2a2c9dc5dc | ||
|
|
6723815563 | ||
|
|
7e5591318f | ||
|
|
87ed778efe | ||
|
|
d0ff82801c | ||
|
|
f940290866 | ||
|
|
014060370a | ||
|
|
8c222b9e05 | ||
|
|
95f0c8bf51 | ||
|
|
a127711b86 | ||
|
|
715c531512 | ||
|
|
e6508a5bbc | ||
|
|
88d17e4c04 | ||
|
|
9ab8570153 | ||
|
|
8f2507a945 | ||
|
|
befffc573c | ||
|
|
945faac770 | ||
|
|
c8b1686ce4 | ||
|
|
ba92ccad14 | ||
|
|
012e453997 | ||
|
|
79b95c8cc6 | ||
|
|
34d0f40ee7 | ||
|
|
8421134420 | ||
|
|
a7470615be | ||
|
|
33b09d29e1 | ||
|
|
a9afd2c116 | ||
|
|
2502befb42 | ||
|
|
c3fae847f3 | ||
|
|
7f53351920 | ||
|
|
bb95a7d6cd | ||
|
|
5c8dfe5456 | ||
|
|
ab8c168e3b | ||
|
|
3a477e4d01 | ||
|
|
96bfc32fe2 | ||
|
|
f135842071 | ||
|
|
a9bc525f22 | ||
|
|
5c9102bd9a | ||
|
|
c556f3471b | ||
|
|
2fb6124412 | ||
|
|
e482b56f58 | ||
|
|
de671863e2 | ||
|
|
e0cabb9f8c | ||
|
|
3d9c545f59 | ||
|
|
9b944ee8c2 | ||
|
|
509677abfd | ||
|
|
addc1e8e25 | ||
|
|
faf69da4b0 | ||
|
|
76e3b4fb0f | ||
|
|
e8bdbf975a |
@@ -50,20 +50,21 @@ ForEachMacros: [Q_FOREACH, BOOST_FOREACH]
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: "^<(test)/"
|
||||
Priority: 0
|
||||
- Regex: "^<(xrpld)/"
|
||||
Priority: 1
|
||||
- Regex: "^<(xrpl)/"
|
||||
- Regex: "^<(xrpld)/"
|
||||
Priority: 2
|
||||
- Regex: "^<(boost)/"
|
||||
- Regex: "^<(xrpl)/"
|
||||
Priority: 3
|
||||
- Regex: "^.*/"
|
||||
- Regex: "^<(boost)/"
|
||||
Priority: 4
|
||||
- Regex: '^.*\.h'
|
||||
- Regex: "^.*/"
|
||||
Priority: 5
|
||||
- Regex: ".*"
|
||||
- Regex: '^.*\.h'
|
||||
Priority: 6
|
||||
- Regex: ".*"
|
||||
Priority: 7
|
||||
IncludeIsMainRegex: "$"
|
||||
MainIncludeChar: AngleBracket
|
||||
IndentCaseLabels: true
|
||||
IndentFunctionDeclarationAfterType: false
|
||||
IndentRequiresClause: true
|
||||
|
||||
389
.clang-tidy
389
.clang-tidy
@@ -1,199 +1,208 @@
|
||||
---
|
||||
Checks: "-*,
|
||||
bugprone-argument-comment,
|
||||
bugprone-assert-side-effect,
|
||||
bugprone-bad-signal-to-kill-thread,
|
||||
bugprone-bool-pointer-implicit-conversion,
|
||||
bugprone-casting-through-void,
|
||||
bugprone-chained-comparison,
|
||||
bugprone-compare-pointer-to-member-virtual-function,
|
||||
bugprone-copy-constructor-init,
|
||||
bugprone-crtp-constructor-accessibility,
|
||||
bugprone-dangling-handle,
|
||||
bugprone-dynamic-static-initializers,
|
||||
bugprone-empty-catch,
|
||||
bugprone-fold-init-type,
|
||||
bugprone-forward-declaration-namespace,
|
||||
bugprone-inaccurate-erase,
|
||||
bugprone-inc-dec-in-conditions,
|
||||
bugprone-incorrect-enable-if,
|
||||
bugprone-incorrect-roundings,
|
||||
bugprone-infinite-loop,
|
||||
bugprone-integer-division,
|
||||
bugprone-lambda-function-name,
|
||||
bugprone-macro-parentheses,
|
||||
bugprone-macro-repeated-side-effects,
|
||||
bugprone-misplaced-operator-in-strlen-in-alloc,
|
||||
bugprone-misplaced-pointer-arithmetic-in-alloc,
|
||||
bugprone-misplaced-widening-cast,
|
||||
bugprone-move-forwarding-reference,
|
||||
bugprone-multi-level-implicit-pointer-conversion,
|
||||
bugprone-multiple-new-in-one-expression,
|
||||
bugprone-multiple-statement-macro,
|
||||
bugprone-no-escape,
|
||||
bugprone-non-zero-enum-to-bool-conversion,
|
||||
bugprone-optional-value-conversion,
|
||||
bugprone-parent-virtual-call,
|
||||
bugprone-pointer-arithmetic-on-polymorphic-object,
|
||||
bugprone-posix-return,
|
||||
bugprone-redundant-branch-condition,
|
||||
bugprone-reserved-identifier,
|
||||
bugprone-return-const-ref-from-parameter,
|
||||
bugprone-shared-ptr-array-mismatch,
|
||||
bugprone-signal-handler,
|
||||
bugprone-signed-char-misuse,
|
||||
bugprone-sizeof-container,
|
||||
bugprone-sizeof-expression,
|
||||
bugprone-spuriously-wake-up-functions,
|
||||
bugprone-standalone-empty,
|
||||
bugprone-string-constructor,
|
||||
bugprone-string-integer-assignment,
|
||||
bugprone-string-literal-with-embedded-nul,
|
||||
bugprone-stringview-nullptr,
|
||||
bugprone-suspicious-enum-usage,
|
||||
bugprone-suspicious-include,
|
||||
bugprone-suspicious-memory-comparison,
|
||||
bugprone-suspicious-memset-usage,
|
||||
bugprone-suspicious-missing-comma,
|
||||
bugprone-suspicious-realloc-usage,
|
||||
bugprone-suspicious-semicolon,
|
||||
bugprone-suspicious-string-compare,
|
||||
bugprone-suspicious-stringview-data-usage,
|
||||
bugprone-swapped-arguments,
|
||||
bugprone-switch-missing-default-case,
|
||||
bugprone-terminating-continue,
|
||||
bugprone-throw-keyword-missing,
|
||||
bugprone-too-small-loop-variable,
|
||||
# bugprone-unchecked-optional-access, # see https://github.com/XRPLF/rippled/pull/6502
|
||||
bugprone-undefined-memory-manipulation,
|
||||
bugprone-undelegated-constructor,
|
||||
bugprone-unhandled-exception-at-new,
|
||||
bugprone-unhandled-self-assignment,
|
||||
bugprone-unique-ptr-array-mismatch,
|
||||
bugprone-unsafe-functions,
|
||||
bugprone-use-after-move,
|
||||
bugprone-unused-raii,
|
||||
bugprone-unused-return-value,
|
||||
bugprone-unused-local-non-trivial-variable,
|
||||
bugprone-virtual-near-miss,
|
||||
cppcoreguidelines-init-variables,
|
||||
cppcoreguidelines-misleading-capture-default-by-value,
|
||||
cppcoreguidelines-no-suspend-with-lock,
|
||||
cppcoreguidelines-pro-type-member-init,
|
||||
cppcoreguidelines-pro-type-static-cast-downcast,
|
||||
cppcoreguidelines-rvalue-reference-param-not-moved,
|
||||
cppcoreguidelines-use-default-member-init,
|
||||
cppcoreguidelines-virtual-class-destructor,
|
||||
hicpp-ignored-remove-result,
|
||||
misc-definitions-in-headers,
|
||||
misc-header-include-cycle,
|
||||
misc-misplaced-const,
|
||||
misc-static-assert,
|
||||
misc-throw-by-value-catch-by-reference,
|
||||
misc-unused-alias-decls,
|
||||
misc-unused-using-decls,
|
||||
modernize-deprecated-headers,
|
||||
modernize-make-shared,
|
||||
modernize-make-unique,
|
||||
performance-implicit-conversion-in-loop,
|
||||
performance-move-constructor-init,
|
||||
performance-trivially-destructible,
|
||||
readability-avoid-nested-conditional-operator,
|
||||
readability-avoid-return-with-void-value,
|
||||
readability-braces-around-statements,
|
||||
readability-const-return-type,
|
||||
readability-container-contains,
|
||||
readability-container-size-empty,
|
||||
readability-convert-member-functions-to-static,
|
||||
readability-duplicate-include,
|
||||
readability-else-after-return,
|
||||
readability-enum-initial-value,
|
||||
readability-implicit-bool-conversion,
|
||||
readability-make-member-function-const,
|
||||
readability-math-missing-parentheses,
|
||||
readability-misleading-indentation,
|
||||
readability-non-const-parameter,
|
||||
readability-redundant-casting,
|
||||
readability-redundant-declaration,
|
||||
readability-redundant-inline-specifier,
|
||||
readability-redundant-member-init,
|
||||
readability-redundant-string-init,
|
||||
readability-reference-to-constructed-temporary,
|
||||
readability-simplify-boolean-expr,
|
||||
readability-static-definition-in-anonymous-namespace,
|
||||
readability-suspicious-call-argument,
|
||||
readability-use-std-min-max
|
||||
bugprone-*,
|
||||
-bugprone-assignment-in-if-condition,
|
||||
-bugprone-bitwise-pointer-cast,
|
||||
-bugprone-branch-clone,
|
||||
-bugprone-command-processor,
|
||||
-bugprone-copy-constructor-mutates-argument,
|
||||
-bugprone-default-operator-new-on-overaligned-type,
|
||||
-bugprone-easily-swappable-parameters,
|
||||
-bugprone-exception-copy-constructor-throws,
|
||||
-bugprone-exception-escape,
|
||||
-bugprone-float-loop-counter,
|
||||
-bugprone-forwarding-reference-overload,
|
||||
-bugprone-implicit-widening-of-multiplication-result,
|
||||
-bugprone-incorrect-enable-shared-from-this,
|
||||
-bugprone-narrowing-conversions,
|
||||
-bugprone-nondeterministic-pointer-iteration-order,
|
||||
-bugprone-not-null-terminated-result,
|
||||
-bugprone-random-generator-seed,
|
||||
-bugprone-raw-memory-call-on-non-trivial-type,
|
||||
-bugprone-std-namespace-modification,
|
||||
-bugprone-tagged-union-member-count,
|
||||
-bugprone-throwing-static-initialization,
|
||||
-bugprone-unchecked-string-to-number-conversion,
|
||||
-bugprone-unintended-char-ostream-output,
|
||||
|
||||
cppcoreguidelines-*,
|
||||
-cppcoreguidelines-avoid-c-arrays,
|
||||
-cppcoreguidelines-avoid-capturing-lambda-coroutines,
|
||||
-cppcoreguidelines-avoid-const-or-ref-data-members,
|
||||
-cppcoreguidelines-avoid-do-while,
|
||||
-cppcoreguidelines-avoid-goto,
|
||||
-cppcoreguidelines-avoid-magic-numbers,
|
||||
-cppcoreguidelines-avoid-non-const-global-variables,
|
||||
-cppcoreguidelines-avoid-reference-coroutine-parameters,
|
||||
-cppcoreguidelines-c-copy-assignment-signature,
|
||||
-cppcoreguidelines-explicit-virtual-functions,
|
||||
-cppcoreguidelines-interfaces-global-init,
|
||||
-cppcoreguidelines-macro-to-enum,
|
||||
-cppcoreguidelines-macro-usage,
|
||||
-cppcoreguidelines-missing-std-forward,
|
||||
-cppcoreguidelines-narrowing-conversions,
|
||||
-cppcoreguidelines-no-malloc,
|
||||
-cppcoreguidelines-noexcept-destructor,
|
||||
-cppcoreguidelines-noexcept-move-operations,
|
||||
-cppcoreguidelines-noexcept-swap,
|
||||
-cppcoreguidelines-non-private-member-variables-in-classes,
|
||||
-cppcoreguidelines-owning-memory,
|
||||
-cppcoreguidelines-prefer-member-initializer,
|
||||
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
||||
-cppcoreguidelines-pro-bounds-avoid-unchecked-container-access,
|
||||
-cppcoreguidelines-pro-bounds-constant-array-index,
|
||||
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
|
||||
-cppcoreguidelines-pro-type-const-cast,
|
||||
-cppcoreguidelines-pro-type-cstyle-cast,
|
||||
-cppcoreguidelines-pro-type-reinterpret-cast,
|
||||
-cppcoreguidelines-pro-type-union-access,
|
||||
-cppcoreguidelines-pro-type-vararg,
|
||||
-cppcoreguidelines-slicing,
|
||||
-cppcoreguidelines-special-member-functions,
|
||||
|
||||
llvm-namespace-comment,
|
||||
|
||||
misc-*,
|
||||
-misc-anonymous-namespace-in-header,
|
||||
-misc-confusable-identifiers,
|
||||
-misc-coroutine-hostile-raii,
|
||||
-misc-misleading-bidirectional,
|
||||
-misc-misleading-identifier,
|
||||
-misc-multiple-inheritance,
|
||||
-misc-new-delete-overloads,
|
||||
-misc-no-recursion,
|
||||
-misc-non-copyable-objects,
|
||||
-misc-non-private-member-variables-in-classes,
|
||||
-misc-override-with-different-visibility,
|
||||
-misc-predictable-rand,
|
||||
-misc-unconventional-assign-operator,
|
||||
-misc-uniqueptr-reset-release,
|
||||
-misc-unused-parameters,
|
||||
-misc-use-anonymous-namespace,
|
||||
-misc-use-internal-linkage,
|
||||
|
||||
modernize-*,
|
||||
-modernize-avoid-bind,
|
||||
-modernize-avoid-c-arrays,
|
||||
-modernize-avoid-c-style-cast,
|
||||
-modernize-avoid-setjmp-longjmp,
|
||||
-modernize-avoid-variadic-functions,
|
||||
-modernize-deprecated-ios-base-aliases,
|
||||
-modernize-loop-convert,
|
||||
-modernize-macro-to-enum,
|
||||
-modernize-min-max-use-initializer-list,
|
||||
-modernize-raw-string-literal,
|
||||
-modernize-redundant-void-arg,
|
||||
-modernize-replace-auto-ptr,
|
||||
-modernize-replace-disallow-copy-and-assign-macro,
|
||||
-modernize-replace-random-shuffle,
|
||||
-modernize-return-braced-init-list,
|
||||
-modernize-shrink-to-fit,
|
||||
-modernize-unary-static-assert,
|
||||
-modernize-use-auto,
|
||||
-modernize-use-bool-literals,
|
||||
-modernize-use-constraints,
|
||||
-modernize-use-default-member-init,
|
||||
-modernize-use-integer-sign-comparison,
|
||||
-modernize-use-noexcept,
|
||||
-modernize-use-nullptr,
|
||||
-modernize-use-std-format,
|
||||
-modernize-use-std-print,
|
||||
-modernize-use-trailing-return-type,
|
||||
-modernize-use-transparent-functors,
|
||||
-modernize-use-uncaught-exceptions,
|
||||
|
||||
performance-*,
|
||||
-performance-avoid-endl,
|
||||
-performance-enum-size,
|
||||
-performance-inefficient-algorithm,
|
||||
-performance-inefficient-string-concatenation,
|
||||
-performance-no-int-to-ptr,
|
||||
-performance-noexcept-destructor,
|
||||
-performance-noexcept-move-constructor,
|
||||
-performance-noexcept-swap,
|
||||
-performance-type-promotion-in-math-fn,
|
||||
-performance-unnecessary-copy-initialization,
|
||||
-performance-unnecessary-value-param,
|
||||
|
||||
readability-*,
|
||||
-readability-avoid-const-params-in-decls,
|
||||
-readability-avoid-unconditional-preprocessor-if,
|
||||
-readability-container-data-pointer,
|
||||
-readability-delete-null-pointer,
|
||||
-readability-function-cognitive-complexity,
|
||||
-readability-function-size,
|
||||
-readability-identifier-length,
|
||||
-readability-inconsistent-declaration-parameter-name,
|
||||
-readability-isolate-declaration,
|
||||
-readability-magic-numbers,
|
||||
-readability-misplaced-array-index,
|
||||
-readability-named-parameter,
|
||||
-readability-operators-representation,
|
||||
-readability-qualified-auto,
|
||||
-readability-redundant-access-specifiers,
|
||||
-readability-redundant-control-flow,
|
||||
-readability-redundant-function-ptr-dereference,
|
||||
-readability-redundant-preprocessor,
|
||||
-readability-redundant-smartptr-get,
|
||||
-readability-redundant-string-cstr,
|
||||
-readability-simplify-subscript-expr,
|
||||
-readability-static-accessed-through-instance,
|
||||
-readability-string-compare,
|
||||
-readability-uniqueptr-delete-release,
|
||||
-readability-uppercase-literal-suffix,
|
||||
-readability-use-anyofallof,
|
||||
-readability-use-concise-preprocessor-directives
|
||||
"
|
||||
# ---
|
||||
# checks that have some issues that need to be resolved:
|
||||
#
|
||||
# llvm-namespace-comment,
|
||||
# misc-const-correctness,
|
||||
# misc-include-cleaner,
|
||||
# misc-redundant-expression,
|
||||
#
|
||||
# readability-inconsistent-declaration-parameter-name, # in this codebase this check will break a lot of arg names
|
||||
# readability-static-accessed-through-instance, # this check is probably unnecessary. it makes the code less readable
|
||||
# readability-identifier-naming,
|
||||
#
|
||||
# modernize-concat-nested-namespaces,
|
||||
# modernize-pass-by-value,
|
||||
# modernize-type-traits,
|
||||
# modernize-use-designated-initializers,
|
||||
# modernize-use-emplace,
|
||||
# modernize-use-equals-default,
|
||||
# modernize-use-equals-delete,
|
||||
# modernize-use-override,
|
||||
# modernize-use-ranges,
|
||||
# modernize-use-starts-ends-with,
|
||||
# modernize-use-std-numbers,
|
||||
# modernize-use-using,
|
||||
#
|
||||
# performance-faster-string-find,
|
||||
# performance-for-range-copy,
|
||||
# performance-inefficient-vector-operation,
|
||||
# performance-move-const-arg,
|
||||
# performance-no-automatic-move,
|
||||
# bugprone-narrowing-conversions, # This will break a lot of code but we should enable it in the future because it can eliminate a lot of bugs
|
||||
# misc-override-with-different-visibility, # Will be addressed in a future PR, but for now it generates too many warnings
|
||||
# readability-inconsistent-declaration-parameter-name, # In this codebase this check will break a lot of arg names
|
||||
# readability-static-accessed-through-instance, # this check is probably unnecessary. It makes the code less readable
|
||||
# ---
|
||||
#
|
||||
|
||||
CheckOptions:
|
||||
readability-braces-around-statements.ShortStatementLines: 2
|
||||
# readability-identifier-naming.MacroDefinitionCase: UPPER_CASE
|
||||
# readability-identifier-naming.ClassCase: CamelCase
|
||||
# readability-identifier-naming.StructCase: CamelCase
|
||||
# readability-identifier-naming.UnionCase: CamelCase
|
||||
# readability-identifier-naming.EnumCase: CamelCase
|
||||
# readability-identifier-naming.EnumConstantCase: CamelCase
|
||||
# readability-identifier-naming.ScopedEnumConstantCase: CamelCase
|
||||
# readability-identifier-naming.GlobalConstantCase: UPPER_CASE
|
||||
# readability-identifier-naming.GlobalConstantPrefix: "k"
|
||||
# readability-identifier-naming.GlobalVariableCase: CamelCase
|
||||
# readability-identifier-naming.GlobalVariablePrefix: "g"
|
||||
# readability-identifier-naming.ConstexprFunctionCase: camelBack
|
||||
# readability-identifier-naming.ConstexprMethodCase: camelBack
|
||||
# readability-identifier-naming.ClassMethodCase: camelBack
|
||||
# readability-identifier-naming.ClassMemberCase: camelBack
|
||||
# readability-identifier-naming.ClassConstantCase: UPPER_CASE
|
||||
# readability-identifier-naming.ClassConstantPrefix: "k"
|
||||
# readability-identifier-naming.StaticConstantCase: UPPER_CASE
|
||||
# readability-identifier-naming.StaticConstantPrefix: "k"
|
||||
# readability-identifier-naming.StaticVariableCase: UPPER_CASE
|
||||
# readability-identifier-naming.StaticVariablePrefix: "k"
|
||||
# readability-identifier-naming.ConstexprVariableCase: UPPER_CASE
|
||||
# readability-identifier-naming.ConstexprVariablePrefix: "k"
|
||||
# readability-identifier-naming.LocalConstantCase: camelBack
|
||||
# readability-identifier-naming.LocalVariableCase: camelBack
|
||||
# readability-identifier-naming.TemplateParameterCase: CamelCase
|
||||
# readability-identifier-naming.ParameterCase: camelBack
|
||||
# readability-identifier-naming.FunctionCase: camelBack
|
||||
# readability-identifier-naming.MemberCase: camelBack
|
||||
# readability-identifier-naming.PrivateMemberSuffix: _
|
||||
# readability-identifier-naming.ProtectedMemberSuffix: _
|
||||
# readability-identifier-naming.PublicMemberSuffix: ""
|
||||
# readability-identifier-naming.FunctionIgnoredRegexp: ".*tag_invoke.*"
|
||||
bugprone-unsafe-functions.ReportMoreUnsafeFunctions: true
|
||||
bugprone-unused-return-value.CheckedReturnTypes: ::std::error_code;::std::error_condition;::std::errc
|
||||
# misc-include-cleaner.IgnoreHeaders: '.*/(detail|impl)/.*;.*(expected|unexpected).*;.*ranges_lower_bound\.h;time.h;stdlib.h;__chrono/.*;fmt/chrono.h;boost/uuid/uuid_hash.hpp'
|
||||
#
|
||||
# HeaderFilterRegex: '^.*/(src|tests)/.*\.(h|hpp)$'
|
||||
|
||||
misc-include-cleaner.IgnoreHeaders: ".*/(detail|impl)/.*;.*fwd\\.h(pp)?;time.h;stdlib.h;sqlite3.h;netinet/in\\.h;sys/resource\\.h;sys/sysinfo\\.h;linux/sysinfo\\.h;__chrono/.*;bits/.*;_abort\\.h;boost/uuid/uuid_hash.hpp;boost/beast/core/flat_buffer\\.hpp;boost/beast/http/field\\.hpp;boost/beast/http/dynamic_body\\.hpp;boost/beast/http/message\\.hpp;boost/beast/http/read\\.hpp;boost/beast/http/write\\.hpp;openssl/obj_mac\\.h"
|
||||
|
||||
readability-braces-around-statements.ShortStatementLines: 2
|
||||
readability-identifier-naming.MacroDefinitionCase: UPPER_CASE
|
||||
readability-identifier-naming.ClassCase: CamelCase
|
||||
readability-identifier-naming.StructCase: CamelCase
|
||||
readability-identifier-naming.UnionCase: CamelCase
|
||||
readability-identifier-naming.EnumCase: CamelCase
|
||||
readability-identifier-naming.EnumConstantCase: CamelCase
|
||||
readability-identifier-naming.ScopedEnumConstantCase: CamelCase
|
||||
readability-identifier-naming.GlobalConstantCase: CamelCase
|
||||
readability-identifier-naming.GlobalConstantPrefix: "k"
|
||||
readability-identifier-naming.GlobalVariableCase: CamelCase
|
||||
readability-identifier-naming.GlobalVariablePrefix: "g"
|
||||
readability-identifier-naming.ConstexprFunctionCase: camelBack
|
||||
readability-identifier-naming.ConstexprMethodCase: camelBack
|
||||
readability-identifier-naming.ClassMethodCase: camelBack
|
||||
readability-identifier-naming.ClassMemberCase: camelBack
|
||||
readability-identifier-naming.ClassConstantCase: CamelCase
|
||||
readability-identifier-naming.ClassConstantPrefix: "k"
|
||||
readability-identifier-naming.StaticConstantCase: CamelCase
|
||||
readability-identifier-naming.StaticConstantPrefix: "k"
|
||||
readability-identifier-naming.StaticVariableCase: camelBack
|
||||
readability-identifier-naming.ConstexprVariableCase: camelBack
|
||||
readability-identifier-naming.LocalConstantCase: camelBack
|
||||
readability-identifier-naming.LocalVariableCase: camelBack
|
||||
readability-identifier-naming.TemplateParameterCase: CamelCase
|
||||
readability-identifier-naming.ParameterCase: camelBack
|
||||
readability-identifier-naming.FunctionCase: camelBack
|
||||
readability-identifier-naming.MemberCase: camelBack
|
||||
readability-identifier-naming.PrivateMemberCase: camelBack
|
||||
readability-identifier-naming.PrivateMemberSuffix: _
|
||||
readability-identifier-naming.ProtectedMemberCase: camelBack
|
||||
readability-identifier-naming.ProtectedMemberSuffix: _
|
||||
readability-identifier-naming.PublicMemberCase: camelBack
|
||||
readability-identifier-naming.PublicMemberSuffix: ""
|
||||
readability-identifier-naming.GlobalFunctionIgnoredRegexp: "^(to_string|hash_append|tuple_hash)$"
|
||||
|
||||
HeaderFilterRegex: '^.*/(tests?|xrpl|xrpld)/.*\.(h|hpp|ipp)$'
|
||||
ExcludeHeaderFilterRegex: '^.*/protocol_autogen/.*\.(h|hpp)$'
|
||||
WarningsAsErrors: "*"
|
||||
|
||||
@@ -36,3 +36,8 @@ ignore:
|
||||
- "src/tests/"
|
||||
- "include/xrpl/beast/test/"
|
||||
- "include/xrpl/beast/unit_test/"
|
||||
# Telemetry modules — conditionally compiled behind XRPL_ENABLE_TELEMETRY,
|
||||
# which is not enabled in coverage builds.
|
||||
- "src/xrpld/telemetry/"
|
||||
- "src/libxrpl/beast/insight/OTelCollector.cpp"
|
||||
- "include/xrpl/beast/insight/OTelCollector.h"
|
||||
|
||||
@@ -11,9 +11,6 @@ endfunction()
|
||||
function(create_symbolic_link target link)
|
||||
endfunction()
|
||||
|
||||
function(xrpl_add_test name)
|
||||
endfunction()
|
||||
|
||||
macro(exclude_from_default target_)
|
||||
endmacro()
|
||||
|
||||
@@ -99,3 +96,6 @@ function(verbose_find_path variable name)
|
||||
${ARGN}
|
||||
)
|
||||
endfunction()
|
||||
|
||||
function(patch_nix_binary target)
|
||||
endfunction()
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
# This file is sorted in reverse chronological order, with the most recent commits at the top.
|
||||
# The commits listed here are ignored by git blame, which is useful for formatting-only commits that would otherwise obscure the history of changes to a file.
|
||||
|
||||
# refactor: Enable clang-tidy `readability-identifier-naming` check (#6571)
|
||||
8995564ed6b9e453e144bb663303072a3c1ba305
|
||||
# refactor: Enable remaining clang-tidy `cppcoreguidelines` checks (#6538)
|
||||
72f4cb097f626b08b02fc3efcb4aa11cb2e7adb8
|
||||
# refactor: Rename system name from 'ripple' to 'xrpld' (#6347)
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/feature_request.md
vendored
4
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest a new feature for the rippled project
|
||||
title: "[Title with short description] (Version: [rippled version])"
|
||||
about: Suggest a new feature for the xrpld project
|
||||
title: "[Title with short description] (Version: [xrpld version])"
|
||||
labels: Feature Request
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
19
.github/actions/build-deps/action.yml
vendored
19
.github/actions/build-deps/action.yml
vendored
@@ -35,14 +35,13 @@ runs:
|
||||
LOG_VERBOSITY: ${{ inputs.log_verbosity }}
|
||||
SANITIZERS: ${{ inputs.sanitizers }}
|
||||
run: |
|
||||
echo 'Installing dependencies.'
|
||||
conan install \
|
||||
--profile ci \
|
||||
--build="${BUILD_OPTION}" \
|
||||
--options:host='&:tests=True' \
|
||||
--options:host='&:xrpld=True' \
|
||||
--settings:all build_type="${BUILD_TYPE}" \
|
||||
--conf:all tools.build:jobs=${BUILD_NPROC} \
|
||||
--conf:all tools.build:verbosity="${LOG_VERBOSITY}" \
|
||||
--conf:all tools.compilation:verbosity="${LOG_VERBOSITY}" \
|
||||
.
|
||||
--profile:all ci \
|
||||
--build="${BUILD_OPTION}" \
|
||||
--options:host='&:tests=True' \
|
||||
--options:host='&:xrpld=True' \
|
||||
--settings:all build_type="${BUILD_TYPE}" \
|
||||
--conf:all tools.build:jobs=${BUILD_NPROC} \
|
||||
--conf:all tools.build:verbosity="${LOG_VERBOSITY}" \
|
||||
--conf:all tools.compilation:verbosity="${LOG_VERBOSITY}" \
|
||||
.
|
||||
|
||||
10
.github/actions/generate-version/action.yml
vendored
10
.github/actions/generate-version/action.yml
vendored
@@ -15,7 +15,7 @@ runs:
|
||||
shell: bash
|
||||
env:
|
||||
VERSION: ${{ github.ref_name }}
|
||||
run: echo "VERSION=${VERSION}" >> "${GITHUB_ENV}"
|
||||
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.
|
||||
@@ -28,17 +28,17 @@ runs:
|
||||
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
|
||||
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}"
|
||||
echo "VERSION=${VERSION}" >>"${GITHUB_ENV}"
|
||||
|
||||
- name: Output version
|
||||
id: version
|
||||
shell: bash
|
||||
run: echo "version=${VERSION}" >> "${GITHUB_OUTPUT}"
|
||||
run: echo "version=${VERSION}" >>"${GITHUB_OUTPUT}"
|
||||
|
||||
43
.github/actions/print-env/action.yml
vendored
43
.github/actions/print-env/action.yml
vendored
@@ -1,43 +0,0 @@
|
||||
name: Print build environment
|
||||
description: "Print environment and some tooling versions"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Check configuration (Windows)
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'Checking environment variables.'
|
||||
set
|
||||
|
||||
- name: Check configuration (Linux and macOS)
|
||||
if: ${{ runner.os == 'Linux' || runner.os == 'macOS' }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'Checking path.'
|
||||
echo ${PATH} | tr ':' '\n'
|
||||
|
||||
echo 'Checking environment variables.'
|
||||
env | sort
|
||||
|
||||
echo 'Checking compiler version.'
|
||||
${{ runner.os == 'Linux' && '${CC}' || 'clang' }} --version
|
||||
|
||||
echo 'Checking Ninja version.'
|
||||
ninja --version
|
||||
|
||||
echo 'Checking nproc version.'
|
||||
nproc --version
|
||||
|
||||
- name: Check configuration (all)
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'Checking Ccache version.'
|
||||
ccache --version
|
||||
|
||||
echo 'Checking CMake version.'
|
||||
cmake --version
|
||||
|
||||
echo 'Checking Conan version.'
|
||||
conan --version
|
||||
34
.github/actions/set-compiler-env/action.yml
vendored
Normal file
34
.github/actions/set-compiler-env/action.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Set compiler environment
|
||||
description: "Set CC and CXX environment variables for the given compiler."
|
||||
|
||||
inputs:
|
||||
compiler:
|
||||
description: 'The compiler to use ("gcc" or "clang").'
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
- name: Set CC and CXX for gcc
|
||||
if: ${{ inputs.compiler == 'gcc' }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo "CC=gcc" >>"${GITHUB_ENV}"
|
||||
echo "CXX=g++" >>"${GITHUB_ENV}"
|
||||
|
||||
- name: Set CC and CXX for clang
|
||||
if: ${{ inputs.compiler == 'clang' }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo "CC=clang" >>"${GITHUB_ENV}"
|
||||
echo "CXX=clang++" >>"${GITHUB_ENV}"
|
||||
|
||||
- name: Fail on unknown compiler
|
||||
if: ${{ inputs.compiler != 'gcc' && inputs.compiler != 'clang' }}
|
||||
shell: bash
|
||||
env:
|
||||
COMPILER: ${{ inputs.compiler }}
|
||||
run: |
|
||||
echo "Unknown compiler: $COMPILER" >&2
|
||||
exit 1
|
||||
27
.github/actions/setup-conan/action.yml
vendored
27
.github/actions/setup-conan/action.yml
vendored
@@ -9,38 +9,41 @@ inputs:
|
||||
remote_url:
|
||||
description: "The URL of the Conan endpoint to use."
|
||||
required: false
|
||||
default: https://conan.ripplex.io
|
||||
default: https://conan.xrplf.org/repository/conan/
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
- name: Set up Conan configuration
|
||||
- name: Apply custom configuration to global.conf
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'Installing configuration.'
|
||||
cat conan/global.conf ${{ runner.os == 'Linux' && '>>' || '>' }} $(conan config home)/global.conf
|
||||
|
||||
echo 'Conan configuration:'
|
||||
conan config show '*'
|
||||
|
||||
- name: Set up Conan profile
|
||||
- name: Show global configuration
|
||||
shell: bash
|
||||
run: |
|
||||
conan config show '*'
|
||||
|
||||
- name: Install profiles
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'Installing profile.'
|
||||
conan config install conan/profiles/ -tf $(conan config home)/profiles/
|
||||
|
||||
echo 'Conan profile:'
|
||||
- name: Show CI profile
|
||||
shell: bash
|
||||
run: |
|
||||
conan profile show --profile ci
|
||||
|
||||
- name: Set up Conan remote
|
||||
- name: Add a remote
|
||||
shell: bash
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
REMOTE_URL: ${{ inputs.remote_url }}
|
||||
run: |
|
||||
echo "Adding Conan remote '${REMOTE_NAME}' at '${REMOTE_URL}'."
|
||||
conan remote add --index 0 --force "${REMOTE_NAME}" "${REMOTE_URL}"
|
||||
|
||||
echo 'Listing Conan remotes.'
|
||||
- name: List remotes
|
||||
shell: bash
|
||||
run: |
|
||||
conan remote list
|
||||
|
||||
51
.github/dependabot.yml
vendored
51
.github/dependabot.yml
vendored
@@ -1,51 +1,12 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "04:00"
|
||||
timezone: Etc/GMT
|
||||
commit-message:
|
||||
prefix: "ci: [DEPENDABOT] "
|
||||
target-branch: develop
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: .github/actions/build-deps/
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "04:00"
|
||||
timezone: Etc/GMT
|
||||
commit-message:
|
||||
prefix: "ci: [DEPENDABOT] "
|
||||
target-branch: develop
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: .github/actions/generate-version/
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "04:00"
|
||||
timezone: Etc/GMT
|
||||
commit-message:
|
||||
prefix: "ci: [DEPENDABOT] "
|
||||
target-branch: develop
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: .github/actions/print-env/
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "04:00"
|
||||
timezone: Etc/GMT
|
||||
commit-message:
|
||||
prefix: "ci: [DEPENDABOT] "
|
||||
target-branch: develop
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: .github/actions/setup-conan/
|
||||
directories:
|
||||
- /
|
||||
- .github/actions/build-deps/
|
||||
- .github/actions/generate-version/
|
||||
- .github/actions/set-compiler-env/
|
||||
- .github/actions/setup-conan/
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
|
||||
85
.github/scripts/check-pr-description.py
vendored
Normal file
85
.github/scripts/check-pr-description.py
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Checks that a pull request description has been customized from the
|
||||
pull_request_template.md. Exits with code 1 if the description is empty
|
||||
or identical to the template (ignoring HTML comments and whitespace).
|
||||
|
||||
Usage:
|
||||
python check-pr-description.py --template-file TEMPLATE --pr-body-file BODY
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def normalize(text: str) -> str:
|
||||
"""Strip HTML comments, trim lines, and remove blank lines."""
|
||||
# Remove HTML comments (possibly multi-line)
|
||||
text = re.sub(r"<!--.*?-->", "", text, flags=re.DOTALL)
|
||||
# Strip each line and drop empties
|
||||
lines = [line.strip() for line in text.splitlines()]
|
||||
lines = [line for line in lines if line]
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Check that a PR description differs from the template."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--template-file",
|
||||
type=Path,
|
||||
required=True,
|
||||
help="Path to the pull request template file.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pr-body-file",
|
||||
type=Path,
|
||||
required=True,
|
||||
help="Path to a file containing the PR body text.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
template_path: Path = args.template_file
|
||||
pr_body_path: Path = args.pr_body_file
|
||||
|
||||
if not template_path.is_file():
|
||||
print(f"::error::Template file {template_path} not found")
|
||||
return 1
|
||||
|
||||
if not pr_body_path.is_file():
|
||||
print(f"::error::PR body file {pr_body_path} not found")
|
||||
return 1
|
||||
|
||||
template = template_path.read_text(encoding="utf-8")
|
||||
pr_body = pr_body_path.read_text(encoding="utf-8")
|
||||
|
||||
# Check if the PR body is empty or whitespace-only
|
||||
if not pr_body.strip():
|
||||
print(
|
||||
"::error::PR description is empty. "
|
||||
"Please fill in the pull request template."
|
||||
)
|
||||
return 1
|
||||
|
||||
norm_template = normalize(template)
|
||||
norm_pr_body = normalize(pr_body)
|
||||
|
||||
if norm_pr_body == norm_template:
|
||||
print(
|
||||
"::error::PR description (ignoring HTML comments) is identical"
|
||||
" to the template. Please fill in the details of your change."
|
||||
f"\n\nVisible template content:\n---\n{norm_template}\n---"
|
||||
f"\n\nVisible PR description content:\n---\n{norm_pr_body}\n---"
|
||||
)
|
||||
return 1
|
||||
|
||||
print("PR description has been customized from the template.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
403
.github/scripts/format-inline-bash.py
vendored
Executable file
403
.github/scripts/format-inline-bash.py
vendored
Executable file
@@ -0,0 +1,403 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Format embedded shell snippets using the shfmt hook configured in
|
||||
.pre-commit-config.yaml.
|
||||
|
||||
Two shapes are recognised:
|
||||
|
||||
* YAML workflow/action files: literal block-scalar runs (`run: |`) and
|
||||
single-line runs (`run: some command`). A single-line run is upgraded to
|
||||
a `run: |` block scalar if shfmt's output spans multiple lines.
|
||||
|
||||
* Markdown files: ``` ```bash ``` fenced code blocks.
|
||||
|
||||
Any block that shfmt cannot parse is skipped with a warning on stderr, so
|
||||
the file is left untouched and surrounding blocks still get formatted.
|
||||
|
||||
For each occurrence the body is dedented, written to a temp .sh file,
|
||||
formatted via `pre-commit run shfmt --files <temp>` (falling back to
|
||||
`prek`), then re-indented and written back in place.
|
||||
|
||||
When invoked without arguments, every .yml/.yaml under .github/ plus every
|
||||
.md file in the repo is scanned. When invoked with file arguments (the
|
||||
pre-commit case), only those files are processed.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
|
||||
_HOOK_RUNNER = next((cmd for cmd in ("pre-commit", "prek") if shutil.which(cmd)), None)
|
||||
if _HOOK_RUNNER is None:
|
||||
sys.exit("error: neither `pre-commit` nor `prek` found on PATH")
|
||||
|
||||
RUN_BLOCK_RE = re.compile(r"^(?P<prefix>[ \t]*(?:- )?)run:[ \t]*\|[+-]?[ \t]*$")
|
||||
RUN_INLINE_RE = re.compile(
|
||||
r"^(?P<prefix>[ \t]*(?:- )?)run:[ \t]+" r"(?P<value>(?!\|[+-]?[ \t]*$)\S.*?)[ \t]*$"
|
||||
)
|
||||
MD_BASH_OPEN_RE = re.compile(r"^(?P<indent>[ ]{0,3})`{3}bash[ \t]*$")
|
||||
MD_FENCE_CLOSE_RE = re.compile(r"^[ ]{0,3}`{3,}[ \t]*$")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BlockRun:
|
||||
"""A `run: |` block scalar; `body_start:body_end` slices into `lines`."""
|
||||
|
||||
body_start: int
|
||||
body_end: int
|
||||
body_indent: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class InlineRun:
|
||||
"""A single-line `run: value` at `line_idx`."""
|
||||
|
||||
line_idx: int
|
||||
prefix: str
|
||||
value: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MdBashBlock:
|
||||
"""A markdown ``` ```bash ``` fenced code block.
|
||||
|
||||
`body_start:body_end` slices into the file's lines; `open_line_idx`
|
||||
points at the opening fence line.
|
||||
"""
|
||||
|
||||
open_line_idx: int
|
||||
body_start: int
|
||||
body_end: int
|
||||
body_indent: int
|
||||
|
||||
|
||||
RunItem = Union[BlockRun, InlineRun]
|
||||
|
||||
|
||||
def _scan_block_body(
|
||||
lines: list[str], body_start: int, run_col: int
|
||||
) -> tuple[int | None, int]:
|
||||
"""Locate the body of a `run: |` block scalar starting at `body_start`.
|
||||
|
||||
Returns `(body_indent, scan_end)`. `scan_end` is the line index where the
|
||||
outer scanner should resume. `body_indent` is `None` when no body is
|
||||
present (the scalar is empty, or the next non-blank line has indent
|
||||
`<= run_col`).
|
||||
"""
|
||||
body_indent: int | None = None
|
||||
scan_end = len(lines)
|
||||
for idx in range(body_start, len(lines)):
|
||||
line = lines[idx]
|
||||
if line.strip() == "":
|
||||
continue
|
||||
indent = len(line) - len(line.lstrip(" "))
|
||||
if body_indent is None:
|
||||
if indent > run_col:
|
||||
body_indent = indent
|
||||
else:
|
||||
scan_end = idx
|
||||
break
|
||||
elif indent < body_indent:
|
||||
scan_end = idx
|
||||
break
|
||||
if body_indent is not None:
|
||||
while scan_end > body_start and lines[scan_end - 1].strip() == "":
|
||||
scan_end -= 1
|
||||
if scan_end <= body_start:
|
||||
body_indent = None
|
||||
return body_indent, scan_end
|
||||
|
||||
|
||||
def find_run_blocks(lines: list[str]) -> list[RunItem]:
|
||||
"""Return run items in document order."""
|
||||
items: list[RunItem] = []
|
||||
line_idx = 0
|
||||
while line_idx < len(lines):
|
||||
line = lines[line_idx]
|
||||
if block_match := RUN_BLOCK_RE.match(line):
|
||||
run_col = len(block_match.group("prefix"))
|
||||
body_start = line_idx + 1
|
||||
body_indent, scan_end = _scan_block_body(lines, body_start, run_col)
|
||||
if body_indent is not None:
|
||||
items.append(
|
||||
BlockRun(
|
||||
body_start=body_start,
|
||||
body_end=scan_end,
|
||||
body_indent=body_indent,
|
||||
)
|
||||
)
|
||||
line_idx = scan_end
|
||||
continue
|
||||
if inline_match := RUN_INLINE_RE.match(line):
|
||||
items.append(
|
||||
InlineRun(
|
||||
line_idx=line_idx,
|
||||
prefix=inline_match.group("prefix"),
|
||||
value=inline_match.group("value"),
|
||||
)
|
||||
)
|
||||
line_idx += 1
|
||||
return items
|
||||
|
||||
|
||||
def find_md_bash_blocks(lines: list[str]) -> list[MdBashBlock]:
|
||||
"""Return ``` ```bash ``` fenced code blocks in document order."""
|
||||
blocks: list[MdBashBlock] = []
|
||||
line_idx = 0
|
||||
while line_idx < len(lines):
|
||||
open_match = MD_BASH_OPEN_RE.match(lines[line_idx])
|
||||
if not open_match:
|
||||
line_idx += 1
|
||||
continue
|
||||
body_start = line_idx + 1
|
||||
close_idx = next(
|
||||
(
|
||||
j
|
||||
for j in range(body_start, len(lines))
|
||||
if MD_FENCE_CLOSE_RE.match(lines[j])
|
||||
),
|
||||
None,
|
||||
)
|
||||
if close_idx is None:
|
||||
line_idx = body_start
|
||||
continue
|
||||
body = lines[body_start:close_idx]
|
||||
non_blank = [b for b in body if b.strip()]
|
||||
body_indent = (
|
||||
min(len(b) - len(b.lstrip(" ")) for b in non_blank)
|
||||
if non_blank
|
||||
else len(open_match.group("indent"))
|
||||
)
|
||||
blocks.append(
|
||||
MdBashBlock(
|
||||
open_line_idx=line_idx,
|
||||
body_start=body_start,
|
||||
body_end=close_idx,
|
||||
body_indent=body_indent,
|
||||
)
|
||||
)
|
||||
line_idx = close_idx + 1
|
||||
return blocks
|
||||
|
||||
|
||||
def dedent(lines: list[str], n: int) -> list[str]:
|
||||
pad = " " * n
|
||||
return [
|
||||
(
|
||||
""
|
||||
if line.strip() == ""
|
||||
else (line[n:] if line.startswith(pad) else line.lstrip(" "))
|
||||
)
|
||||
for line in lines
|
||||
]
|
||||
|
||||
|
||||
def reindent(lines: list[str], n: int) -> list[str]:
|
||||
pad = " " * n
|
||||
return [pad + line if line else "" for line in lines]
|
||||
|
||||
|
||||
_SHFMT_ERR_RE = re.compile(r"\.sh:\d+:\d+:\s")
|
||||
_GHA_EXPR_RE = re.compile(r"\$\{\{.*?\}\}", re.DOTALL)
|
||||
_GHA_PLACEHOLDER_RE = re.compile(r"__GHA_EXPR_(\d+)__")
|
||||
|
||||
|
||||
def _encode_gha_exprs(text: str) -> tuple[str, list[str]]:
|
||||
"""Replace `${{ ... }}` expressions with bash-safe placeholder identifiers."""
|
||||
exprs: list[str] = []
|
||||
|
||||
def repl(match: re.Match[str]) -> str:
|
||||
exprs.append(match.group(0))
|
||||
return f"__GHA_EXPR_{len(exprs) - 1}__"
|
||||
|
||||
return _GHA_EXPR_RE.sub(repl, text), exprs
|
||||
|
||||
|
||||
def _decode_gha_exprs(text: str, exprs: list[str]) -> str:
|
||||
"""Restore `${{ ... }}` expressions from placeholder identifiers."""
|
||||
return _GHA_PLACEHOLDER_RE.sub(lambda m: exprs[int(m.group(1))], text)
|
||||
|
||||
|
||||
def shfmt_via_hook(tmp_path: Path) -> tuple[bool, str]:
|
||||
# `${{ ... }}` is not valid shell, so swap it for a placeholder identifier
|
||||
# that shfmt can parse, then restore it after formatting.
|
||||
encoded, exprs = _encode_gha_exprs(tmp_path.read_text())
|
||||
if exprs:
|
||||
tmp_path.write_text(encoded)
|
||||
res = subprocess.run(
|
||||
[_HOOK_RUNNER, "run", "shfmt", "--files", str(tmp_path)],
|
||||
cwd=REPO,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
output = res.stdout + res.stderr
|
||||
# shfmt emits parse errors as "<path>:<line>:<col>: <message>".
|
||||
parse_err = bool(_SHFMT_ERR_RE.search(output))
|
||||
# A non-zero exit that is neither a parse error nor pre-commit's "I had
|
||||
# to modify files" signal means the hook itself failed to run (missing
|
||||
# binary, install failure, bad config, ...). Surface that loudly rather
|
||||
# than silently treating it as a no-op.
|
||||
if (
|
||||
res.returncode != 0
|
||||
and not parse_err
|
||||
and "files were modified by this hook" not in output
|
||||
):
|
||||
sys.exit(
|
||||
f"error: `{_HOOK_RUNNER} run shfmt` failed with exit {res.returncode}:\n{output}"
|
||||
)
|
||||
if exprs and not parse_err:
|
||||
tmp_path.write_text(_decode_gha_exprs(tmp_path.read_text(), exprs))
|
||||
return not parse_err, output
|
||||
|
||||
|
||||
def _skip(path: Path, where: int, kind: str, output: str) -> None:
|
||||
print(
|
||||
f" shfmt could not parse {kind} at {path}:{where + 1} — skipped",
|
||||
file=sys.stderr,
|
||||
)
|
||||
print(f" {output.strip()}", file=sys.stderr)
|
||||
|
||||
|
||||
def process_yaml_file(path: Path, tmp_path: Path) -> int:
|
||||
text = path.read_text()
|
||||
had_nl = text.endswith("\n")
|
||||
lines = text.split("\n")
|
||||
if had_nl:
|
||||
lines = lines[:-1]
|
||||
items = find_run_blocks(lines)
|
||||
if not items:
|
||||
return 0
|
||||
changed = 0
|
||||
# Process in reverse so earlier indices remain valid as we splice.
|
||||
for item in reversed(items):
|
||||
if isinstance(item, BlockRun):
|
||||
body = lines[item.body_start : item.body_end]
|
||||
tmp_path.write_text("\n".join(dedent(body, item.body_indent)) + "\n")
|
||||
ok, output = shfmt_via_hook(tmp_path)
|
||||
if not ok:
|
||||
_skip(path, item.body_start, "block", output)
|
||||
continue
|
||||
formatted = tmp_path.read_text().rstrip("\n")
|
||||
new_body = reindent(formatted.split("\n"), item.body_indent)
|
||||
if new_body != body:
|
||||
lines[item.body_start : item.body_end] = new_body
|
||||
changed += 1
|
||||
else:
|
||||
tmp_path.write_text(item.value + "\n")
|
||||
ok, output = shfmt_via_hook(tmp_path)
|
||||
if not ok:
|
||||
_skip(path, item.line_idx, "inline run", output)
|
||||
continue
|
||||
formatted = tmp_path.read_text().rstrip("\n")
|
||||
if formatted == item.value:
|
||||
continue
|
||||
formatted_lines = formatted.split("\n")
|
||||
if len(formatted_lines) == 1:
|
||||
lines[item.line_idx] = f"{item.prefix}run: {formatted}"
|
||||
else:
|
||||
body_indent = len(item.prefix) + 2
|
||||
lines[item.line_idx : item.line_idx + 1] = [
|
||||
f"{item.prefix}run: |",
|
||||
*reindent(formatted_lines, body_indent),
|
||||
]
|
||||
changed += 1
|
||||
new_text = "\n".join(lines) + ("\n" if had_nl else "")
|
||||
if new_text != text:
|
||||
path.write_text(new_text)
|
||||
return changed
|
||||
|
||||
|
||||
def process_md_file(path: Path, tmp_path: Path) -> int:
|
||||
text = path.read_text()
|
||||
had_nl = text.endswith("\n")
|
||||
lines = text.split("\n")
|
||||
if had_nl:
|
||||
lines = lines[:-1]
|
||||
blocks = find_md_bash_blocks(lines)
|
||||
if not blocks:
|
||||
return 0
|
||||
changed = 0
|
||||
for block in reversed(blocks):
|
||||
body = lines[block.body_start : block.body_end]
|
||||
tmp_path.write_text("\n".join(dedent(body, block.body_indent)) + "\n")
|
||||
ok, output = shfmt_via_hook(tmp_path)
|
||||
if not ok:
|
||||
_skip(path, block.open_line_idx, "```bash block", output)
|
||||
continue
|
||||
formatted = tmp_path.read_text().rstrip("\n")
|
||||
formatted_lines = formatted.split("\n") if formatted else []
|
||||
new_body = reindent(formatted_lines, block.body_indent)
|
||||
if new_body != body:
|
||||
lines[block.body_start : block.body_end] = new_body
|
||||
changed += 1
|
||||
new_text = "\n".join(lines) + ("\n" if had_nl else "")
|
||||
if new_text != text:
|
||||
path.write_text(new_text)
|
||||
return changed
|
||||
|
||||
|
||||
def process_file(path: Path, tmp_path: Path) -> int:
|
||||
if path.suffix in (".yml", ".yaml"):
|
||||
return process_yaml_file(path, tmp_path)
|
||||
if path.suffix == ".md":
|
||||
return process_md_file(path, tmp_path)
|
||||
return 0
|
||||
|
||||
|
||||
def gather_files(argv: list[str]) -> list[Path]:
|
||||
"""Return YAML workflow/action files and markdown files that we should
|
||||
process — either the paths in `argv` or, when `argv` is empty, every
|
||||
such file in the repo (skipping `external/`)."""
|
||||
if argv:
|
||||
candidates: list[Path] = [
|
||||
(REPO / a).resolve() if not Path(a).is_absolute() else Path(a) for a in argv
|
||||
]
|
||||
else:
|
||||
gh = REPO / ".github"
|
||||
candidates = [
|
||||
*gh.rglob("*.yml"),
|
||||
*gh.rglob("*.yaml"),
|
||||
*(
|
||||
p
|
||||
for p in REPO.rglob("*.md")
|
||||
if "external" not in p.relative_to(REPO).parts
|
||||
),
|
||||
]
|
||||
return sorted(
|
||||
p
|
||||
for p in candidates
|
||||
if p.exists()
|
||||
and (
|
||||
(p.suffix in (".yml", ".yaml") and ".github" in p.parts)
|
||||
or p.suffix == ".md"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
files = gather_files(argv)
|
||||
if not files:
|
||||
return 0
|
||||
with tempfile.TemporaryDirectory(prefix="format-inline-bash-") as tmpdir:
|
||||
tmp_path = Path(tmpdir) / "shfmt.sh"
|
||||
total = 0
|
||||
for f in files:
|
||||
n = process_file(f, tmp_path)
|
||||
if n:
|
||||
print(f"{f.relative_to(REPO)}: reformatted {n} block(s)")
|
||||
total += n
|
||||
return 1 if total else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
12
.github/scripts/levelization/README.md
vendored
12
.github/scripts/levelization/README.md
vendored
@@ -1,14 +1,14 @@
|
||||
# Levelization
|
||||
|
||||
Levelization is the term used to describe efforts to prevent rippled from
|
||||
Levelization is the term used to describe efforts to prevent xrpld from
|
||||
having or creating cyclic dependencies.
|
||||
|
||||
rippled code is organized into directories under `src/xrpld`, `src/libxrpl` (and
|
||||
xrpld code is organized into directories under `src/xrpld`, `src/libxrpl` (and
|
||||
`src/test`) representing modules. The modules are intended to be
|
||||
organized into "tiers" or "levels" such that a module from one level can
|
||||
only include code from lower levels. Additionally, a module
|
||||
in one level should never include code in an `impl` or `detail` folder of any level
|
||||
other than it's own.
|
||||
other than its own.
|
||||
|
||||
The codebase is split into two main areas:
|
||||
|
||||
@@ -22,7 +22,7 @@ levelization violations they find (by moving files or individual
|
||||
classes). At the very least, don't make things worse.
|
||||
|
||||
The table below summarizes the _desired_ division of modules, based on the current
|
||||
state of the rippled code. The levels are numbered from
|
||||
state of the xrpld code. The levels are numbered from
|
||||
the bottom up with the lower level, lower numbered, more independent
|
||||
modules listed first, and the higher level, higher numbered modules with
|
||||
more dependencies listed later.
|
||||
@@ -72,10 +72,10 @@ that `test` code should _never_ be included in `xrpl` or `xrpld` code.)
|
||||
|
||||
The [levelization](generate.py) script takes no parameters,
|
||||
reads no environment variables, and can be run from any directory,
|
||||
as long as it is in the expected location in the rippled repo.
|
||||
as long as it is in the expected location in the xrpld repo.
|
||||
It can be run at any time from within a checked out repo, and will
|
||||
do an analysis of all the `#include`s in
|
||||
the rippled source. The only caveat is that it runs much slower
|
||||
the xrpld source. The only caveat is that it runs much slower
|
||||
under Windows than in Linux. It hasn't yet been tested under MacOS.
|
||||
It generates many files of [results](results):
|
||||
|
||||
|
||||
0
.github/scripts/levelization/generate.py
vendored
Normal file → Executable file
0
.github/scripts/levelization/generate.py
vendored
Normal file → Executable file
17
.github/scripts/levelization/results/loops.txt
vendored
17
.github/scripts/levelization/results/loops.txt
vendored
@@ -2,20 +2,29 @@ Loop: test.jtx test.toplevel
|
||||
test.toplevel > test.jtx
|
||||
|
||||
Loop: test.jtx test.unit_test
|
||||
test.unit_test == test.jtx
|
||||
test.unit_test ~= test.jtx
|
||||
|
||||
Loop: xrpl.telemetry xrpld.rpc
|
||||
xrpld.rpc > xrpl.telemetry
|
||||
|
||||
Loop: xrpld.app xrpld.overlay
|
||||
xrpld.overlay ~= xrpld.app
|
||||
xrpld.app > xrpld.overlay
|
||||
|
||||
Loop: xrpld.app xrpld.peerfinder
|
||||
xrpld.peerfinder == xrpld.app
|
||||
xrpld.peerfinder ~= xrpld.app
|
||||
|
||||
Loop: xrpld.app xrpld.rpc
|
||||
xrpld.rpc > xrpld.app
|
||||
|
||||
Loop: xrpld.app xrpld.shamap
|
||||
xrpld.shamap ~= xrpld.app
|
||||
xrpld.shamap > xrpld.app
|
||||
|
||||
Loop: xrpld.app xrpld.telemetry
|
||||
xrpld.telemetry ~= xrpld.app
|
||||
|
||||
Loop: xrpld.overlay xrpld.rpc
|
||||
xrpld.rpc ~= xrpld.overlay
|
||||
|
||||
Loop: xrpld.overlay xrpld.telemetry
|
||||
xrpld.telemetry ~= xrpld.overlay
|
||||
|
||||
|
||||
103
.github/scripts/levelization/results/ordering.txt
vendored
103
.github/scripts/levelization/results/ordering.txt
vendored
@@ -1,38 +1,53 @@
|
||||
libxrpl.basics > xrpl.basics
|
||||
libxrpl.conditions > xrpl.basics
|
||||
libxrpl.conditions > xrpl.conditions
|
||||
libxrpl.config > xrpl.basics
|
||||
libxrpl.config > xrpl.config
|
||||
libxrpl.core > xrpl.basics
|
||||
libxrpl.core > xrpl.core
|
||||
libxrpl.core > xrpl.json
|
||||
libxrpl.crypto > xrpl.basics
|
||||
libxrpl.json > xrpl.basics
|
||||
libxrpl.json > xrpl.json
|
||||
libxrpl.ledger > xrpl.basics
|
||||
libxrpl.ledger > xrpl.json
|
||||
libxrpl.ledger > xrpl.ledger
|
||||
libxrpl.ledger > xrpl.nodestore
|
||||
libxrpl.ledger > xrpl.protocol
|
||||
libxrpl.ledger > xrpl.shamap
|
||||
libxrpl.net > xrpl.basics
|
||||
libxrpl.net > xrpl.net
|
||||
libxrpl.nodestore > xrpl.basics
|
||||
libxrpl.nodestore > xrpl.config
|
||||
libxrpl.nodestore > xrpl.json
|
||||
libxrpl.nodestore > xrpl.nodestore
|
||||
libxrpl.nodestore > xrpl.protocol
|
||||
libxrpl.protocol > xrpl.basics
|
||||
libxrpl.protocol > xrpl.json
|
||||
libxrpl.protocol > xrpl.protocol
|
||||
libxrpl.protocol_autogen > xrpl.protocol_autogen
|
||||
libxrpl.rdb > xrpl.basics
|
||||
libxrpl.rdb > xrpl.config
|
||||
libxrpl.rdb > xrpl.core
|
||||
libxrpl.rdb > xrpl.rdb
|
||||
libxrpl.resource > xrpl.basics
|
||||
libxrpl.resource > xrpl.json
|
||||
libxrpl.resource > xrpl.protocol
|
||||
libxrpl.resource > xrpl.resource
|
||||
libxrpl.server > xrpl.basics
|
||||
libxrpl.server > xrpl.config
|
||||
libxrpl.server > xrpl.core
|
||||
libxrpl.server > xrpl.json
|
||||
libxrpl.server > xrpl.protocol
|
||||
libxrpl.server > xrpl.rdb
|
||||
libxrpl.server > xrpl.resource
|
||||
libxrpl.server > xrpl.server
|
||||
libxrpl.shamap > xrpl.basics
|
||||
libxrpl.shamap > xrpl.nodestore
|
||||
libxrpl.shamap > xrpl.protocol
|
||||
libxrpl.shamap > xrpl.shamap
|
||||
libxrpl.telemetry > xrpl.basics
|
||||
libxrpl.telemetry > xrpl.config
|
||||
libxrpl.telemetry > xrpl.telemetry
|
||||
libxrpl.tx > xrpl.basics
|
||||
libxrpl.tx > xrpl.conditions
|
||||
libxrpl.tx > xrpl.core
|
||||
@@ -40,14 +55,15 @@ libxrpl.tx > xrpl.json
|
||||
libxrpl.tx > xrpl.ledger
|
||||
libxrpl.tx > xrpl.protocol
|
||||
libxrpl.tx > xrpl.server
|
||||
libxrpl.tx > xrpl.telemetry
|
||||
libxrpl.tx > xrpl.tx
|
||||
test.app > test.jtx
|
||||
test.app > test.rpc
|
||||
test.app > test.toplevel
|
||||
test.app > test.unit_test
|
||||
test.app > xrpl.basics
|
||||
test.app > xrpl.config
|
||||
test.app > xrpl.core
|
||||
test.app > xrpld.app
|
||||
test.app > xrpld.consensus
|
||||
test.app > xrpld.core
|
||||
test.app > xrpld.overlay
|
||||
test.app > xrpld.rpc
|
||||
@@ -55,9 +71,9 @@ test.app > xrpl.json
|
||||
test.app > xrpl.ledger
|
||||
test.app > xrpl.nodestore
|
||||
test.app > xrpl.protocol
|
||||
test.app > xrpl.rdb
|
||||
test.app > xrpl.resource
|
||||
test.app > xrpl.server
|
||||
test.app > xrpl.shamap
|
||||
test.app > xrpl.tx
|
||||
test.basics > test.jtx
|
||||
test.basics > test.unit_test
|
||||
@@ -70,21 +86,23 @@ test.beast > xrpl.basics
|
||||
test.conditions > xrpl.basics
|
||||
test.conditions > xrpl.conditions
|
||||
test.consensus > test.csf
|
||||
test.consensus > test.toplevel
|
||||
test.consensus > test.jtx
|
||||
test.consensus > test.unit_test
|
||||
test.consensus > xrpl.basics
|
||||
test.consensus > xrpld.app
|
||||
test.consensus > xrpld.consensus
|
||||
test.consensus > xrpl.json
|
||||
test.consensus > xrpl.ledger
|
||||
test.consensus > xrpl.protocol
|
||||
test.consensus > xrpl.shamap
|
||||
test.consensus > xrpl.tx
|
||||
test.core > test.jtx
|
||||
test.core > test.toplevel
|
||||
test.core > test.unit_test
|
||||
test.core > xrpl.basics
|
||||
test.core > xrpl.config
|
||||
test.core > xrpl.core
|
||||
test.core > xrpld.core
|
||||
test.core > xrpl.json
|
||||
test.core > xrpl.protocol
|
||||
test.core > xrpl.rdb
|
||||
test.core > xrpl.server
|
||||
test.csf > xrpl.basics
|
||||
@@ -92,9 +110,11 @@ test.csf > xrpld.consensus
|
||||
test.csf > xrpl.json
|
||||
test.csf > xrpl.ledger
|
||||
test.csf > xrpl.protocol
|
||||
test.csf > xrpl.telemetry
|
||||
test.json > test.jtx
|
||||
test.json > xrpl.json
|
||||
test.jtx > xrpl.basics
|
||||
test.jtx > xrpl.config
|
||||
test.jtx > xrpl.core
|
||||
test.jtx > xrpld.app
|
||||
test.jtx > xrpld.core
|
||||
@@ -107,27 +127,34 @@ test.jtx > xrpl.resource
|
||||
test.jtx > xrpl.server
|
||||
test.jtx > xrpl.tx
|
||||
test.ledger > test.jtx
|
||||
test.ledger > test.toplevel
|
||||
test.ledger > xrpl.basics
|
||||
test.ledger > xrpl.core
|
||||
test.ledger > xrpld.app
|
||||
test.ledger > xrpld.core
|
||||
test.ledger > xrpl.json
|
||||
test.ledger > xrpl.ledger
|
||||
test.ledger > xrpl.protocol
|
||||
test.nodestore > test.jtx
|
||||
test.nodestore > test.toplevel
|
||||
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.toplevel
|
||||
test.overlay > test.unit_test
|
||||
test.overlay > xrpl.basics
|
||||
test.overlay > xrpl.config
|
||||
test.overlay > xrpld.app
|
||||
test.overlay > xrpld.core
|
||||
test.overlay > xrpld.overlay
|
||||
test.overlay > xrpld.peerfinder
|
||||
test.overlay > xrpl.ledger
|
||||
test.overlay > xrpl.json
|
||||
test.overlay > xrpl.nodestore
|
||||
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
|
||||
@@ -135,7 +162,7 @@ test.peerfinder > xrpl.basics
|
||||
test.peerfinder > xrpld.core
|
||||
test.peerfinder > xrpld.peerfinder
|
||||
test.peerfinder > xrpl.protocol
|
||||
test.protocol > test.toplevel
|
||||
test.protocol > test.jtx
|
||||
test.protocol > test.unit_test
|
||||
test.protocol > xrpl.basics
|
||||
test.protocol > xrpl.json
|
||||
@@ -144,8 +171,8 @@ test.resource > test.unit_test
|
||||
test.resource > xrpl.basics
|
||||
test.resource > xrpl.resource
|
||||
test.rpc > test.jtx
|
||||
test.rpc > test.toplevel
|
||||
test.rpc > xrpl.basics
|
||||
test.rpc > xrpl.config
|
||||
test.rpc > xrpl.core
|
||||
test.rpc > xrpld.app
|
||||
test.rpc > xrpld.core
|
||||
@@ -158,16 +185,17 @@ test.rpc > xrpl.resource
|
||||
test.rpc > xrpl.server
|
||||
test.rpc > xrpl.tx
|
||||
test.server > test.jtx
|
||||
test.server > test.toplevel
|
||||
test.server > test.unit_test
|
||||
test.server > xrpl.basics
|
||||
test.server > xrpl.config
|
||||
test.server > xrpld.app
|
||||
test.server > xrpld.core
|
||||
test.server > xrpld.rpc
|
||||
test.server > xrpl.json
|
||||
test.server > xrpl.protocol
|
||||
test.server > xrpl.server
|
||||
test.shamap > test.unit_test
|
||||
test.shamap > xrpl.basics
|
||||
test.shamap > xrpl.config
|
||||
test.shamap > xrpl.nodestore
|
||||
test.shamap > xrpl.protocol
|
||||
test.shamap > xrpl.shamap
|
||||
@@ -176,23 +204,32 @@ test.toplevel > xrpl.json
|
||||
test.unit_test > xrpl.basics
|
||||
test.unit_test > xrpl.protocol
|
||||
tests.libxrpl > xrpl.basics
|
||||
tests.libxrpl > xrpl.config
|
||||
tests.libxrpl > xrpl.core
|
||||
tests.libxrpl > xrpld.telemetry
|
||||
tests.libxrpl > xrpl.json
|
||||
tests.libxrpl > xrpl.ledger
|
||||
tests.libxrpl > xrpl.net
|
||||
tests.libxrpl > xrpl.nodestore
|
||||
tests.libxrpl > xrpl.protocol
|
||||
tests.libxrpl > xrpl.protocol_autogen
|
||||
tests.libxrpl > xrpl.server
|
||||
tests.libxrpl > xrpl.shamap
|
||||
tests.libxrpl > xrpl.telemetry
|
||||
tests.libxrpl > xrpl.tx
|
||||
xrpl.conditions > xrpl.basics
|
||||
xrpl.conditions > xrpl.protocol
|
||||
xrpl.config > xrpl.basics
|
||||
xrpl.core > xrpl.basics
|
||||
xrpl.core > xrpl.json
|
||||
xrpl.core > xrpl.ledger
|
||||
xrpl.core > xrpl.protocol
|
||||
xrpl.json > xrpl.basics
|
||||
xrpl.ledger > xrpl.basics
|
||||
xrpl.ledger > xrpl.protocol
|
||||
xrpl.ledger > xrpl.server
|
||||
xrpl.ledger > xrpl.shamap
|
||||
xrpl.net > xrpl.basics
|
||||
xrpl.nodestore > xrpl.basics
|
||||
xrpl.nodestore > xrpl.config
|
||||
xrpl.nodestore > xrpl.protocol
|
||||
xrpl.protocol > xrpl.basics
|
||||
xrpl.protocol > xrpl.json
|
||||
@@ -214,12 +251,15 @@ xrpl.server > xrpl.shamap
|
||||
xrpl.shamap > xrpl.basics
|
||||
xrpl.shamap > xrpl.nodestore
|
||||
xrpl.shamap > xrpl.protocol
|
||||
xrpl.telemetry > xrpl.config
|
||||
xrpl.tx > xrpl.basics
|
||||
xrpl.tx > xrpl.core
|
||||
xrpl.tx > xrpl.ledger
|
||||
xrpl.tx > xrpl.protocol
|
||||
xrpl.tx > xrpl.telemetry
|
||||
xrpld.app > test.unit_test
|
||||
xrpld.app > xrpl.basics
|
||||
xrpld.app > xrpl.config
|
||||
xrpld.app > xrpl.core
|
||||
xrpld.app > xrpld.consensus
|
||||
xrpld.app > xrpld.core
|
||||
@@ -232,36 +272,47 @@ xrpld.app > xrpl.rdb
|
||||
xrpld.app > xrpl.resource
|
||||
xrpld.app > xrpl.server
|
||||
xrpld.app > xrpl.shamap
|
||||
xrpld.app > xrpl.telemetry
|
||||
xrpld.app > xrpl.tx
|
||||
xrpld.consensus > xrpl.basics
|
||||
xrpld.consensus > xrpl.json
|
||||
xrpld.consensus > xrpl.ledger
|
||||
xrpld.consensus > xrpl.protocol
|
||||
xrpld.consensus > xrpl.telemetry
|
||||
xrpld.core > xrpl.basics
|
||||
xrpld.core > xrpl.config
|
||||
xrpld.core > xrpl.core
|
||||
xrpld.core > xrpl.json
|
||||
xrpld.core > xrpl.net
|
||||
xrpld.core > xrpl.protocol
|
||||
xrpld.core > xrpl.rdb
|
||||
xrpld.overlay > xrpl.basics
|
||||
xrpld.overlay > xrpl.config
|
||||
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.protocol
|
||||
xrpld.overlay > xrpl.rdb
|
||||
xrpld.overlay > xrpl.resource
|
||||
xrpld.overlay > xrpl.server
|
||||
xrpld.overlay > xrpl.shamap
|
||||
xrpld.overlay > xrpl.telemetry
|
||||
xrpld.overlay > xrpl.tx
|
||||
xrpld.peerfinder > xrpl.basics
|
||||
xrpld.peerfinder > xrpl.config
|
||||
xrpld.peerfinder > xrpld.core
|
||||
xrpld.peerfinder > xrpl.protocol
|
||||
xrpld.peerfinder > xrpl.rdb
|
||||
xrpld.perflog > xrpl.basics
|
||||
xrpld.perflog > xrpl.config
|
||||
xrpld.perflog > xrpl.core
|
||||
xrpld.perflog > xrpld.rpc
|
||||
xrpld.perflog > xrpld.telemetry
|
||||
xrpld.perflog > xrpl.json
|
||||
xrpld.perflog > xrpl.protocol
|
||||
xrpld.rpc > xrpl.basics
|
||||
xrpld.rpc > xrpl.config
|
||||
xrpld.rpc > xrpl.core
|
||||
xrpld.rpc > xrpld.core
|
||||
xrpld.rpc > xrpl.json
|
||||
@@ -272,5 +323,19 @@ xrpld.rpc > xrpl.protocol
|
||||
xrpld.rpc > xrpl.rdb
|
||||
xrpld.rpc > xrpl.resource
|
||||
xrpld.rpc > xrpl.server
|
||||
xrpld.rpc > xrpl.shamap
|
||||
xrpld.rpc > xrpl.tx
|
||||
xrpld.shamap > xrpl.basics
|
||||
xrpld.shamap > xrpld.core
|
||||
xrpld.shamap > xrpl.protocol
|
||||
xrpld.shamap > xrpl.shamap
|
||||
xrpld.telemetry > xrpl.basics
|
||||
xrpld.telemetry > xrpl.core
|
||||
xrpld.telemetry > xrpld.consensus
|
||||
xrpld.telemetry > xrpld.core
|
||||
xrpld.telemetry > xrpl.json
|
||||
xrpld.telemetry > xrpl.nodestore
|
||||
xrpld.telemetry > xrpl.protocol
|
||||
xrpld.telemetry > xrpl.rdb
|
||||
xrpld.telemetry > xrpl.server
|
||||
xrpld.telemetry > xrpl.telemetry
|
||||
|
||||
70
.github/scripts/otel-naming/README.md
vendored
Normal file
70
.github/scripts/otel-naming/README.md
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
# OTel naming-consistency check
|
||||
|
||||
`check_otel_naming.py` enforces the OpenTelemetry span-attribute naming
|
||||
convention documented in
|
||||
[CONTRIBUTING.md](../../../CONTRIBUTING.md#telemetry-span-attribute-naming)
|
||||
across every layer of the telemetry pipeline. The `*SpanNames.h` constants are
|
||||
the single source of truth (L1); every other layer must agree with them.
|
||||
|
||||
## Running locally
|
||||
|
||||
```
|
||||
python .github/scripts/otel-naming/check_otel_naming.py
|
||||
```
|
||||
|
||||
It takes no arguments, can be run from any directory inside the repo, and uses
|
||||
only the Python standard library (no `pip install`, matching the levelization
|
||||
check). A non-zero exit code means a violation was found; the output lists each
|
||||
violation as `RULE | location | token | expected`.
|
||||
|
||||
## What it checks
|
||||
|
||||
The valid key set is **derived dynamically from the OTel code** — there is no
|
||||
hardcoded allowlist:
|
||||
|
||||
- **L1 keys** come from the `namespace attr { ... }` blocks of every
|
||||
`*SpanNames.h`, resolving the `makeStr("x")` / `join(seg::a, seg::b)` DSL
|
||||
(cross-file, so `join(seg::rpc, ...)` resolves `seg::rpc` from the base
|
||||
`SpanNames.h`). Each constant is resolved against **its own** header, so two
|
||||
headers that define a same-named constant (e.g. a base `attr::ledgerHash` and
|
||||
a domain `attr::ledgerHash`) each contribute their real wire key — a later
|
||||
header cannot clobber an earlier one's value in a flat table.
|
||||
- **Legitimate dotted keys** = ONLY the keys the code actually sets as resource
|
||||
attributes, i.e. the entries inside `Telemetry.cpp`'s `Resource::Create({...})`
|
||||
call: the `semconv::service::*` keys (`service.*`) plus any `attr::<name>`
|
||||
constants passed there (`xrpl.network.*`). A dotted key that is _declared_ in a
|
||||
header but never set as a resource attr is a span attribute in resource
|
||||
clothing — a Rule-A violation, even if it lives in the base `SpanNames.h`.
|
||||
|
||||
### Rules (each fails the build, when its inputs are present)
|
||||
|
||||
| Rule | Check |
|
||||
| ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| A | No stray dotted span-attribute key (only the derived resource keys may be dotted). |
|
||||
| G | Attribute keys are `lower_snake_case` (`^[a-z][a-z0-9_]*$` per dot-segment) — no camelCase, UPPERCASE, or spaces. |
|
||||
| F | No string literals as attribute keys or span-name arguments in `setAttribute`/`addEvent`/`span`/`childSpan`. Attribute _values_ are exempt (runtime data); `*SpanNames.h` definitions and test files are exempt. |
|
||||
| B | Every collector `spanmetrics.dimensions` name exists in the L1 key set. |
|
||||
| C | Every Tempo span-filter tag exists in the L1 key set. |
|
||||
| D | Every dashboard label resolves to an L1 span attribute, a native-metric label (L6, emitted by MetricsRegistry), or a Prometheus/Grafana builtin. TraceQL scope prefixes (`span.`/`resource.`/…) are stripped before the L1 lookup. |
|
||||
| E | No dotted `xrpl.<domain>.<field>` attribute key in the runbook (only the L1 resource attrs `xrpl.network.*` may be dotted). Span names, filenames, OTel-standard keys, and metric labels are not flagged. |
|
||||
|
||||
Rule F runs **unconditionally** (it is a purely syntactic check on the
|
||||
call-sites and needs no `*SpanNames.h`), so a code path that calls
|
||||
`SpanGuard::span`/`setAttribute` directly without ever defining a header is
|
||||
still caught.
|
||||
|
||||
### Warnings (printed, never fail the build)
|
||||
|
||||
| Rule | Check |
|
||||
| ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| H | A namespace-qualified constant (e.g. `foo::bar::myKey`) used at a telemetry call-site is not defined in any `*SpanNames.h`. The constant should live in the proper header; defining it in-place bypasses rules A/G/F. Warns rather than fails — the argument may be a legitimately dynamic value, and the header may live on a later branch. Bare locals and `std::` names are not warned. |
|
||||
|
||||
## Presence-gated
|
||||
|
||||
Every rule runs **only when the source files it needs are present** in the tree
|
||||
and is otherwise skipped (printed as `SKIP: <rule> — <reason>`), never failed.
|
||||
This keeps the check correct no matter how telemetry work is split across PRs —
|
||||
a stacked chain, one large PR, or independent per-stage PRs where (for example)
|
||||
the collector config lands before the dashboards. The collector/Tempo/dashboard/
|
||||
runbook layers are introduced in later phases; on a branch without them, only
|
||||
the L1-intrinsic rules (A, G, F) run.
|
||||
885
.github/scripts/otel-naming/check_otel_naming.py
vendored
Normal file
885
.github/scripts/otel-naming/check_otel_naming.py
vendored
Normal file
@@ -0,0 +1,885 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Usage: check_otel_naming.py
|
||||
This script takes no parameters and can be called from any directory inside the
|
||||
repository (it locates the repo root via `git rev-parse`).
|
||||
|
||||
Enforces the OpenTelemetry span-attribute naming convention documented in
|
||||
CONTRIBUTING.md ("Telemetry span attribute naming") across every layer of the
|
||||
telemetry pipeline. The `*SpanNames.h` constants are the single source of truth
|
||||
(L1); every other layer must agree with them.
|
||||
|
||||
Design principles
|
||||
-----------------
|
||||
1. No hardcoded allowlist. The set of valid attribute keys — including which
|
||||
dotted keys are legitimate resource attributes — is derived dynamically by
|
||||
parsing the repository's own OTel code:
|
||||
* `*SpanNames.h` `namespace attr { ... }` blocks (the underscore/bare keys
|
||||
and the `join(seg::..., ...)` dotted resource compositions), and
|
||||
* the keys the code passes to `Resource::Create({ ... })` in Telemetry.cpp
|
||||
(the standard `semconv::service::*` keys -> service.name/version/...).
|
||||
|
||||
2. Presence-gated enforcement. Every rule runs ONLY when the source files it
|
||||
needs are present in the tree, and is otherwise skipped (never failed). This
|
||||
keeps the check correct no matter how work is split across PRs: a stacked
|
||||
chain, one large PR, or independent per-stage PRs where (for example) the
|
||||
collector config lands in a different PR than the dashboards. The check never
|
||||
assumes a file from another phase/PR exists.
|
||||
|
||||
Layers
|
||||
------
|
||||
L1 code : src/**/*SpanNames.h, include/**/*SpanNames.h (ground truth)
|
||||
L1 resource : src/libxrpl/telemetry/Telemetry.cpp (dotted allowlist)
|
||||
L1 callsites : setAttribute/addEvent/span/childSpan in src/**, include/**
|
||||
L2 collector : docker/telemetry/otel-collector-config.yaml (spanmetrics dims)
|
||||
L3 tempo : docker/telemetry/tempo.yaml (span filter tags)
|
||||
L4 dashboards: docker/telemetry/grafana/dashboards/*.json (PromQL labels)
|
||||
L5 runbook : docs/telemetry-runbook.md (attr tables)
|
||||
L6 metrics : MetricsRegistry.cpp instrument labels (native-metric
|
||||
label keys, a valid dashboard-label source besides L1)
|
||||
|
||||
Rules (each FAILS the build, when its inputs are present)
|
||||
---------------------------------------------------------
|
||||
A No stray dotted span-attribute key. A dotted `<a>.<b>` used as a span
|
||||
attribute that is not in the derived resource-key set is a violation.
|
||||
G Attribute keys must be lower_snake_case (^[a-z][a-z0-9_]*$ per segment).
|
||||
Flags camelCase, UPPERCASE, spaces, and other stray characters.
|
||||
F No string literals as attribute keys or span-name arguments. The
|
||||
setAttribute/addEvent key and the span/childSpan prefix/name args must
|
||||
reference a *SpanNames.h constant, never a "literal". Attribute VALUES are
|
||||
exempt (runtime data). Definitions inside *SpanNames.h are exempt, and
|
||||
test files are exempt (they pass arbitrary literals to exercise the API).
|
||||
B Every collector spanmetrics dimension exists in the L1 key set.
|
||||
C Every tempo span-filter tag exists in the L1 key set.
|
||||
D Every dashboard label resolves to an L1 span attribute, an L6
|
||||
native-metric label, or a builtin. TraceQL `span.`/`resource.` scope
|
||||
prefixes are stripped before the L1 lookup.
|
||||
E No dotted `xrpl.<domain>.<field>` attribute key in the runbook (only the
|
||||
L1 resource attrs xrpl.network.* may be dotted). Span names, filenames,
|
||||
OTel-standard keys, and metric labels are not flagged.
|
||||
|
||||
Warnings (printed, but do NOT fail the build)
|
||||
----------------------------------------------
|
||||
H A constant referenced at a telemetry call-site is not defined in any
|
||||
*SpanNames.h. Span constants should live in the corresponding
|
||||
*SpanNames.h (single source of truth); defining one in-place bypasses the
|
||||
naming rules. A warning (not a failure) because the argument may instead
|
||||
be a legitimately dynamic local (e.g. a computed span-name leaf).
|
||||
|
||||
Exit code is non-zero if any present-and-enforced rule finds a violation.
|
||||
Warnings never change the exit code.
|
||||
"""
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Set, Tuple
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Repo location
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def repo_root() -> Path:
|
||||
"""Return the repository root, so the script works from any CWD.
|
||||
|
||||
Exits with a readable message (not a traceback) if git is unavailable or the
|
||||
CWD is outside a repository."""
|
||||
try:
|
||||
out = subprocess.run(
|
||||
["git", "rev-parse", "--show-toplevel"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
print(
|
||||
"error: check_otel_naming.py must be run inside the git repository.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(2)
|
||||
return Path(out.stdout.strip())
|
||||
|
||||
|
||||
def read_source(path: Path) -> str:
|
||||
"""Read a file as UTF-8, tolerating stray non-UTF-8 bytes rather than
|
||||
crashing the whole check on one bad byte."""
|
||||
return path.read_text(encoding="utf-8", errors="ignore")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Regexes (compiled once)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# A segment/string constant definition: `inline constexpr auto NAME = <expr>;`
|
||||
CONST_DEF = re.compile(r"inline\s+constexpr\s+auto\s+(\w+)\s*=\s*(.+?);", re.DOTALL)
|
||||
MAKESTR = re.compile(r'makeStr\(\s*"([^"]*)"\s*\)')
|
||||
# A `namespace <name> {` opener, to track which namespace a constant lives in.
|
||||
NS_OPEN = re.compile(r"namespace\s+([\w:]+)\s*\{")
|
||||
# A `using ::a::b::field;` re-export inside an attr block; captures the leaf.
|
||||
USING_DECL = re.compile(r"using\s+(?:::)?[\w:]*::(\w+)\s*;")
|
||||
# Telemetry call-sites whose string arguments must be constants, not literals.
|
||||
# Require a receiver so we match real SpanGuard calls, not std::span / a math
|
||||
# `span(...)` / a bare method declaration:
|
||||
# - `SpanGuard::span(` / `SpanGuard::childSpan(` (static factory)
|
||||
# - `<obj>.span(` / `<obj>->setAttribute(` etc. (member call)
|
||||
# `span`/`childSpan` additionally require the `SpanGuard`/`.`/`->` receiver;
|
||||
# `setAttribute`/`addEvent` only ever exist on a guard, so a `.`/`->` suffices.
|
||||
CALLSITE = re.compile(
|
||||
r"(?:SpanGuard::|\.|->)\s*(setAttribute|addEvent|span|childSpan)\s*\("
|
||||
)
|
||||
# A C++ string literal (used to flag literals inside call-site argument lists).
|
||||
STRING_LITERAL = re.compile(r'"((?:[^"\\]|\\.)*)"')
|
||||
# A C++ line comment (`//` ... end of line) and a block comment (`/* ... */`).
|
||||
LINE_COMMENT = re.compile(r"//[^\n]*")
|
||||
BLOCK_COMMENT = re.compile(r"/\*.*?\*/", re.DOTALL)
|
||||
# A TraceQL scope prefix on a label (`span.`, `resource.`, `event.`, etc.).
|
||||
# Dashboards reference span attributes in TraceQL as `span.<attr>`; the bare
|
||||
# attribute is what must exist in L1, so strip the scope before validating.
|
||||
TRACEQL_SCOPE = re.compile(r"^(?:span|resource|event|link|instrumentation_scope)\.")
|
||||
# An OTel metric label key as emitted in C++: `Add(.., {{"label", ...}})` /
|
||||
# `{{"label", value}}` instrument calls in MetricsRegistry.
|
||||
METRIC_LABEL = re.compile(r'\{\{\s*"([a-z_][a-z0-9_]*)"\s*,')
|
||||
|
||||
|
||||
def strip_comments(text: str) -> str:
|
||||
"""Remove C/C++ `//` line comments and `/* ... */` block comments.
|
||||
|
||||
Used only for L1 attribute-key extraction so that a commented-out or
|
||||
illustrative `makeStr("...")` inside a `namespace attr` block does not leak
|
||||
into the authoritative key set. Rule F deliberately does NOT strip comments
|
||||
— it must still see `@code` doc-comment examples so their call-site
|
||||
arguments are held to the constant-only convention.
|
||||
|
||||
String literals are not specially handled; a `//` or `/*` appearing inside a
|
||||
string is vanishingly rare in the *SpanNames.h headers and would at worst
|
||||
drop a constant from L1 (a conservative direction).
|
||||
"""
|
||||
text = BLOCK_COMMENT.sub("", text)
|
||||
text = LINE_COMMENT.sub("", text)
|
||||
return text
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# L1: parse *SpanNames.h into the authoritative key set
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def find_spanname_headers(root: Path) -> List[Path]:
|
||||
return sorted(
|
||||
p
|
||||
for p in list((root / "src").rglob("*SpanNames.h"))
|
||||
+ list((root / "include").rglob("*SpanNames.h"))
|
||||
if p.is_file()
|
||||
)
|
||||
|
||||
|
||||
def resolve_constants(
|
||||
text: str, symbols: Optional[Dict[str, str]] = None
|
||||
) -> Dict[str, str]:
|
||||
"""Resolve `inline constexpr auto NAME = <makeStr/join expr>` to strings.
|
||||
|
||||
Supports the small constexpr DSL used by SpanNames.h:
|
||||
makeStr("x") -> "x"
|
||||
join(a, b) -> resolve(a) + "." + resolve(b)
|
||||
seg::xrpl / attr::foo -> looked up in the symbol table
|
||||
The optional `symbols` argument seeds (and is updated in place with) the
|
||||
table, so a global pass over ALL *SpanNames.h headers can resolve
|
||||
cross-file references such as `join(seg::rpc, ...)` where `seg::rpc` is
|
||||
defined in the base SpanNames.h. Keys are stored by their bare name
|
||||
(last `::` component), so `seg::rpc` and `rpc` both resolve.
|
||||
"""
|
||||
if symbols is None:
|
||||
symbols = {}
|
||||
|
||||
def resolve_expr(expr: str) -> Optional[str]:
|
||||
expr = expr.strip()
|
||||
m = MAKESTR.fullmatch(expr)
|
||||
if m:
|
||||
return m.group(1)
|
||||
if expr.startswith("join(") and expr.endswith(")"):
|
||||
args = split_top_level_args(expr[len("join(") : -1])
|
||||
parts = [resolve_expr(a) for a in args]
|
||||
if any(p is None for p in parts):
|
||||
return None
|
||||
return ".".join(p for p in parts if p is not None)
|
||||
# Bare or qualified symbol reference, e.g. `seg::xrpl` or `networkId`.
|
||||
key = expr.split("::")[-1]
|
||||
return symbols.get(key, symbols.get(expr))
|
||||
|
||||
# Iterate definitions in source order so earlier symbols are available.
|
||||
for m in CONST_DEF.finditer(text):
|
||||
name, expr = m.group(1), m.group(2)
|
||||
val = resolve_expr(expr)
|
||||
if val is not None:
|
||||
symbols[name] = val
|
||||
return symbols
|
||||
|
||||
|
||||
def build_global_symbols(headers: List[Path]) -> Dict[str, str]:
|
||||
"""Resolve constants across ALL headers so cross-file `seg::`/`join`
|
||||
references (e.g. `join(seg::rpc, ...)` in RpcSpanNames.h, where `seg::rpc`
|
||||
lives in the base SpanNames.h) resolve. Base SpanNames.h is processed
|
||||
first so its `seg::` segments seed the table."""
|
||||
symbols: Dict[str, str] = {}
|
||||
ordered = sorted(headers, key=lambda p: (p.name != "SpanNames.h", str(p)))
|
||||
# Two passes: the first seeds segments, the second resolves dependents.
|
||||
# Comments are stripped so a commented-out constant cannot seed the table.
|
||||
for _ in range(2):
|
||||
for h in ordered:
|
||||
resolve_constants(strip_comments(read_source(h)), symbols)
|
||||
return symbols
|
||||
|
||||
|
||||
def split_top_level_args(s: str) -> List[str]:
|
||||
"""Split a comma-separated arg list, respecting nested parentheses and
|
||||
ignoring parens/commas that appear inside a "string literal" (so a value
|
||||
like `setAttribute(k, ",")` does not get mis-split)."""
|
||||
args, depth, cur = [], 0, ""
|
||||
in_str = False
|
||||
escaped = False
|
||||
for ch in s:
|
||||
if in_str:
|
||||
cur += ch
|
||||
if escaped:
|
||||
escaped = False
|
||||
elif ch == "\\":
|
||||
escaped = True
|
||||
elif ch == '"':
|
||||
in_str = False
|
||||
continue
|
||||
if ch == '"':
|
||||
in_str = True
|
||||
cur += ch
|
||||
elif ch == "(":
|
||||
depth += 1
|
||||
cur += ch
|
||||
elif ch == ")":
|
||||
depth -= 1
|
||||
cur += ch
|
||||
elif ch == "," and depth == 0:
|
||||
args.append(cur)
|
||||
cur = ""
|
||||
else:
|
||||
cur += ch
|
||||
if cur.strip():
|
||||
args.append(cur)
|
||||
return args
|
||||
|
||||
|
||||
def attr_namespace_spans(text: str) -> List[str]:
|
||||
"""Return the source text of each `namespace attr { ... }` block in `text`.
|
||||
|
||||
Brace-matched over the whole (comment-stripped) text, so a definition that
|
||||
wraps across several physical lines is contained in one span. Nested braces
|
||||
inside the block are balanced correctly."""
|
||||
spans: List[str] = []
|
||||
for opener in NS_OPEN.finditer(text):
|
||||
if opener.group(1).split("::")[-1] != "attr":
|
||||
continue
|
||||
# Walk from the opening brace, balancing nesting to the matching close.
|
||||
i = opener.end() # one char past the namespace's `{`
|
||||
depth = 1
|
||||
start = i
|
||||
while i < len(text) and depth > 0:
|
||||
c = text[i]
|
||||
if c == "{":
|
||||
depth += 1
|
||||
elif c == "}":
|
||||
depth -= 1
|
||||
i += 1
|
||||
spans.append(text[start : i - 1])
|
||||
return spans
|
||||
|
||||
|
||||
def attr_keys_from_header(path: Path, symbols: Dict[str, str]) -> Set[str]:
|
||||
"""Return the set of attribute-key strings declared in a header's
|
||||
`namespace attr { ... }` block(s). `symbols` is the global cross-file
|
||||
table, used ONLY to seed `seg::`/segment references for `join(...)`
|
||||
resolution — never to look up an attr constant's value.
|
||||
|
||||
A constant DEFINED in this header is resolved against this header's OWN
|
||||
text, so two headers that each define a same-named constant (e.g. the base
|
||||
`attr::ledgerHash = xrpl.ledger.hash` and consensus
|
||||
`attr::ledgerHash = ledger_hash`) each report their real wire key. The
|
||||
global table is keyed by bare name and would otherwise let a later header
|
||||
clobber an earlier one, erasing the real key from L1 (a Rule-A blind spot).
|
||||
A `using`-re-export, by contrast, imports a constant defined elsewhere, so
|
||||
it is resolved against the global table.
|
||||
|
||||
Comments are stripped first (a commented constant must not enter L1), and
|
||||
each attr block is brace-matched over the whole text so multi-line
|
||||
`inline constexpr auto NAME = join(\\n ...);` definitions are captured."""
|
||||
text = strip_comments(read_source(path))
|
||||
# Local table: the global segments/symbols seed cross-file `join` parts,
|
||||
# then this header's own definitions overwrite any same-named global entry
|
||||
# so a locally-defined attr resolves to ITS value, not another header's.
|
||||
local = dict(symbols)
|
||||
resolve_constants(text, local)
|
||||
keys: Set[str] = set()
|
||||
for block in attr_namespace_spans(text):
|
||||
for md in CONST_DEF.finditer(block):
|
||||
# Resolve a locally-defined constant against the LOCAL table; this
|
||||
# captures makeStr("x") and join(seg::y, ...) with the header's own
|
||||
# value, immune to cross-header bare-name collisions.
|
||||
val = local.get(md.group(1))
|
||||
if val is not None:
|
||||
keys.add(val)
|
||||
# `using ::ns::attr::field;` re-exports a constant defined in ANOTHER
|
||||
# header (e.g. PeerSpanNames imports the base ledgerHash). Resolve the
|
||||
# imported name against the global table.
|
||||
for um in USING_DECL.finditer(block):
|
||||
val = symbols.get(um.group(1))
|
||||
if val is not None:
|
||||
keys.add(val)
|
||||
return keys
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Reporting
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class Report:
|
||||
def __init__(self) -> None:
|
||||
self.violations: List[Tuple[str, str, str, str]] = []
|
||||
self.warnings: List[Tuple[str, str, str, str]] = []
|
||||
self.skips: List[str] = []
|
||||
self.checked: List[str] = []
|
||||
|
||||
def violation(self, rule: str, loc: str, token: str, expected: str) -> None:
|
||||
self.violations.append((rule, loc, token, expected))
|
||||
|
||||
def warning(self, rule: str, loc: str, token: str, note: str) -> None:
|
||||
"""A non-fatal finding: printed, but does not fail the build. Used where
|
||||
the script cannot be certain a finding is wrong (e.g. a constant used at
|
||||
a call-site that is not defined in any *SpanNames.h — it might be a
|
||||
misplaced constant, or a legitimately dynamic value)."""
|
||||
self.warnings.append((rule, loc, token, note))
|
||||
|
||||
def skip(self, rule: str, reason: str) -> None:
|
||||
self.skips.append(f"SKIP: {rule} — {reason}")
|
||||
|
||||
def ok(self, msg: str) -> None:
|
||||
self.checked.append(f"OK: {msg}")
|
||||
|
||||
def render_and_exit(self) -> None:
|
||||
for line in self.skips:
|
||||
print(line)
|
||||
for line in self.checked:
|
||||
print(line)
|
||||
if self.warnings:
|
||||
print("\nNaming-convention warnings (non-fatal):\n")
|
||||
print(f" {'RULE':<5} {'LOCATION':<48} {'TOKEN':<28} NOTE")
|
||||
print(f" {'-' * 5} {'-' * 48} {'-' * 28} {'-' * 30}")
|
||||
for rule, loc, token, note in self.warnings:
|
||||
print(f" {rule:<5} {loc:<48} {token:<28} {note}")
|
||||
if self.violations:
|
||||
print("\nNaming-convention violations:\n")
|
||||
print(f" {'RULE':<5} {'LOCATION':<48} {'TOKEN':<28} EXPECTED")
|
||||
print(f" {'-' * 5} {'-' * 48} {'-' * 28} {'-' * 30}")
|
||||
for rule, loc, token, expected in self.violations:
|
||||
print(f" {rule:<5} {loc:<48} {token:<28} {expected}")
|
||||
print(
|
||||
"\nSee CONTRIBUTING.md -> 'Telemetry span attribute naming'. "
|
||||
"The *SpanNames.h constants are the single source of truth."
|
||||
)
|
||||
sys.exit(1)
|
||||
print("\nAll present telemetry naming layers are consistent.")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
root = repo_root()
|
||||
report = Report()
|
||||
|
||||
# --- Build the L1 ground-truth key set (presence-gated) ----------------
|
||||
headers = find_spanname_headers(root)
|
||||
l1_keys: Set[str] = set()
|
||||
if headers:
|
||||
symbols = build_global_symbols(headers)
|
||||
# Map each key to the header(s) that declare it, so Rule A can tell a
|
||||
# legitimate resource attr (declared in the base SpanNames.h) from a
|
||||
# stray dotted key declared in a domain header.
|
||||
keys_by_header: Dict[Path, Set[str]] = {}
|
||||
for h in headers:
|
||||
hk = attr_keys_from_header(h, symbols)
|
||||
keys_by_header[h] = hk
|
||||
l1_keys |= hk
|
||||
report.ok(
|
||||
f"L1: {len(l1_keys)} attribute keys from {len(headers)} "
|
||||
f"*SpanNames.h header(s)"
|
||||
)
|
||||
else:
|
||||
report.skip("L1", "no *SpanNames.h present (not a naming-relevant tree)")
|
||||
keys_by_header = {}
|
||||
|
||||
# --- Derive the legitimate dotted (resource) keys dynamically ----------
|
||||
# ONLY the keys actually passed to Resource::Create() in Telemetry.cpp
|
||||
# (semconv service.* + the attr:: constants set there, e.g. xrpl.network.*).
|
||||
# A dotted key declared in a header but NOT set as a resource attr is a
|
||||
# Rule-A violation, not an allowlist entry.
|
||||
resource_symbols = symbols if headers else {}
|
||||
dotted_allow = derive_dotted_resource_keys(root, resource_symbols, report)
|
||||
|
||||
# --- Rule A: no stray dotted span-attribute keys -----------------------
|
||||
if l1_keys:
|
||||
run_rule_a(keys_by_header, dotted_allow, report)
|
||||
# --- Rule G: keys must be lower_snake_case -----------------------------
|
||||
if l1_keys:
|
||||
run_rule_g(keys_by_header, report)
|
||||
# --- Rule F (+ Rule H): scan telemetry call-sites ----------------------
|
||||
# Runs UNCONDITIONALLY: Rule F is a purely syntactic check (is this argument
|
||||
# a literal?) and does not need the L1 key set, so a code path that uses
|
||||
# SpanGuard::span/setAttribute directly without ever defining a *SpanNames.h
|
||||
# is still caught. Rule H (warning) additionally flags constant references
|
||||
# not defined in any *SpanNames.h.
|
||||
header_symbols = spanname_symbol_names(headers)
|
||||
run_rule_f(root, report, header_symbols)
|
||||
|
||||
# --- Cross-layer rules B/C/D/E (each presence-gated) -------------------
|
||||
# L6 native-metric labels: span attributes are not the only valid dashboard
|
||||
# labels — the MetricsRegistry emits OTel metrics whose label keys are an
|
||||
# additional source of truth. Derive them dynamically (same principle as L1)
|
||||
# so dashboards may reference them without tripping Rule D.
|
||||
metric_labels = metric_label_names(root)
|
||||
run_rule_b_collector(root, l1_keys, report)
|
||||
run_rule_c_tempo(root, l1_keys, report)
|
||||
run_rule_d_dashboards(root, l1_keys, metric_labels, report)
|
||||
run_rule_e_runbook(root, l1_keys, report)
|
||||
|
||||
report.render_and_exit()
|
||||
|
||||
|
||||
def resource_create_block(text: str) -> str:
|
||||
"""Return the text inside the first `Resource::Create({ ... })` argument
|
||||
list, brace-matched so nested `{key, value}` initializers are contained.
|
||||
Empty string if the call is absent."""
|
||||
m = re.search(r"Resource::Create\(\s*\{", text)
|
||||
if not m:
|
||||
return ""
|
||||
i = m.end() # one char past the opening `{`
|
||||
depth, start = 1, i
|
||||
while i < len(text) and depth > 0:
|
||||
c = text[i]
|
||||
if c == "{":
|
||||
depth += 1
|
||||
elif c == "}":
|
||||
depth -= 1
|
||||
i += 1
|
||||
return text[start : i - 1]
|
||||
|
||||
|
||||
def derive_dotted_resource_keys(
|
||||
root: Path, symbols: Dict[str, str], report: Report
|
||||
) -> Set[str]:
|
||||
"""Legitimate dotted keys = ONLY the keys the code actually sets as RESOURCE
|
||||
attributes, i.e. the entries inside Telemetry.cpp's `Resource::Create({...})`
|
||||
call: the standard semconv keys (`service.*`) plus any `attr::<name>`
|
||||
constants passed there (resolved to their wire key via the global symbol
|
||||
table, e.g. `attr::networkId` -> `xrpl.network.id`).
|
||||
|
||||
A dotted key DECLARED in a `*SpanNames.h` header but NOT passed to
|
||||
Resource::Create() is a span attribute wearing the resource form — a Rule-A
|
||||
violation, never allowlisted. Deriving the allowlist from the actual
|
||||
resource call (not from "any dotted key in the base header") is what lets
|
||||
Rule A catch a stray dotted span attr such as `xrpl.ledger.hash`."""
|
||||
allow: Set[str] = set()
|
||||
tele = root / "src" / "libxrpl" / "telemetry" / "Telemetry.cpp"
|
||||
if not tele.is_file():
|
||||
report.skip("resource-derive", "Telemetry.cpp not present")
|
||||
return allow
|
||||
block = resource_create_block(read_source(tele))
|
||||
# semconv::<group>::k<CamelKey> -> the dotted OTel-standard key. The
|
||||
# CamelKey already embeds the group, e.g. service::kServiceInstanceId
|
||||
# -> service.instance.id. Split the CamelCase name into dotted lowercase
|
||||
# segments; if it does not lead with the group, prepend the group.
|
||||
for m in re.finditer(r"semconv::(\w+)::k(\w+)", block):
|
||||
group, camel = m.group(1), m.group(2)
|
||||
segments = camel_to_dotsegments(camel)
|
||||
if segments and segments[0] == group:
|
||||
allow.add(".".join(segments))
|
||||
else:
|
||||
allow.add(group + "." + ".".join(segments))
|
||||
# attr::<name> constants set as resource attrs (e.g. networkId/networkType);
|
||||
# resolve each to its wire key and allowlist only the dotted ones.
|
||||
for m in re.finditer(r"attr::(\w+)", block):
|
||||
val = symbols.get(m.group(1))
|
||||
if val is not None and "." in val:
|
||||
allow.add(val)
|
||||
report.ok(f"resource dotted-key allowlist derived: {sorted(allow)}")
|
||||
return allow
|
||||
|
||||
|
||||
def camel_to_dotsegments(s: str) -> List[str]:
|
||||
"""Split a CamelCase identifier into lowercase dot-segment parts, e.g.
|
||||
`ServiceInstanceId` -> ['service', 'instance', 'id']."""
|
||||
return [w.lower() for w in re.findall(r"[A-Z][a-z0-9]*", s)]
|
||||
|
||||
|
||||
def run_rule_a(
|
||||
keys_by_header: Dict[Path, Set[str]], dotted_allow: Set[str], report: Report
|
||||
) -> None:
|
||||
"""Any dotted attribute key that is not an allowed resource key is a
|
||||
violation, reported against the header that declares it."""
|
||||
found = False
|
||||
for h in sorted(keys_by_header):
|
||||
for key in sorted(keys_by_header[h]):
|
||||
if "." in key and key not in dotted_allow:
|
||||
found = True
|
||||
report.violation("A", h.name, key, "underscore form, not dotted")
|
||||
if not found:
|
||||
report.ok("A: no stray dotted span-attribute keys")
|
||||
|
||||
|
||||
# A lower_snake_case identifier segment: starts lowercase, then lowercase /
|
||||
# digits / underscores. No uppercase, no spaces, no camelCase.
|
||||
SNAKE_SEGMENT = re.compile(r"^[a-z][a-z0-9_]*$")
|
||||
|
||||
|
||||
def run_rule_g(keys_by_header: Dict[Path, Set[str]], report: Report) -> None:
|
||||
"""Every attribute key must be lower_snake_case. Bare/underscore keys must
|
||||
match ^[a-z][a-z0-9_]*$; dotted resource keys must be lowercase
|
||||
dot-separated segments (each segment lower_snake_case). Flags camelCase,
|
||||
UPPERCASE, spaces, and other stray characters."""
|
||||
found = False
|
||||
for h in sorted(keys_by_header):
|
||||
for key in sorted(keys_by_header[h]):
|
||||
segments = key.split(".")
|
||||
if all(SNAKE_SEGMENT.match(seg) for seg in segments):
|
||||
continue
|
||||
found = True
|
||||
report.violation("G", h.name, key, "must be lower_snake_case")
|
||||
if not found:
|
||||
report.ok("G: all attribute keys are lower_snake_case")
|
||||
|
||||
|
||||
# Which argument positions of each call must be a constant (0-based). The
|
||||
# attribute VALUE position is intentionally absent: values are runtime data
|
||||
# (command names, hashes, counts), not naming-convention surface.
|
||||
# setAttribute(key, value) -> check arg 0 (key); value (arg 1) exempt
|
||||
# addEvent(name[, attrs]) -> check arg 0 (event name)
|
||||
# span(category, prefix, name) -> check args 1,2 (prefix + span-name leaf)
|
||||
# childSpan(name[, parentCtx]) -> check arg 0 (span-name leaf)
|
||||
CONSTANT_ARG_POSITIONS: Dict[str, Set[int]] = {
|
||||
"setAttribute": {0},
|
||||
"addEvent": {0},
|
||||
"span": {1, 2},
|
||||
"childSpan": {0},
|
||||
}
|
||||
|
||||
|
||||
def is_test_path(path: Path) -> bool:
|
||||
"""True if the path is test code. Tests legitimately pass arbitrary literal
|
||||
keys/names to exercise the API mechanics, so Rule F does not apply to them.
|
||||
Matches a `test`/`tests` directory anywhere in the path (e.g. src/test/,
|
||||
src/tests/, .../detail/tests/)."""
|
||||
return any(part in ("test", "tests") for part in path.parts)
|
||||
|
||||
|
||||
# A constant reference passed at a call-site, e.g. `rpc_span::attr::command`
|
||||
# or a bare `myKey`. We capture the leaf identifier (after the last `::`).
|
||||
IDENTIFIER_ARG = re.compile(r"^[\s&*]*([A-Za-z_][\w:]*)\s*$")
|
||||
|
||||
|
||||
def spanname_symbol_names(headers: List[Path]) -> Set[str]:
|
||||
"""Every `inline constexpr auto NAME = ...;` symbol defined across the
|
||||
*SpanNames.h headers, by bare name. Used by Rule H to tell whether a
|
||||
constant referenced at a call-site actually lives in a SpanNames header."""
|
||||
names: Set[str] = set()
|
||||
for h in headers:
|
||||
for m in CONST_DEF.finditer(strip_comments(read_source(h))):
|
||||
names.add(m.group(1))
|
||||
return names
|
||||
|
||||
|
||||
def run_rule_f(root: Path, report: Report, header_symbols: Set[str]) -> None:
|
||||
"""Walk every telemetry call-site (non-test, non-*SpanNames.h) and check the
|
||||
constant-only argument positions of setAttribute/addEvent/span/childSpan:
|
||||
|
||||
Rule F (FAIL): a string literal in a key / span-name position. Attribute
|
||||
VALUES are exempt (runtime data).
|
||||
Rule H (WARN): a constant reference whose name is not defined in any
|
||||
*SpanNames.h. The constant should live in the corresponding
|
||||
*SpanNames.h (single source of truth); defining it in-place bypasses
|
||||
the naming rules. Warn rather than fail — the argument may instead be a
|
||||
legitimately dynamic local (e.g. a computed span-name leaf)."""
|
||||
found_f = False
|
||||
sources = [
|
||||
p
|
||||
for base in ("src", "include")
|
||||
for ext in ("*.h", "*.cpp")
|
||||
for p in (root / base).rglob(ext)
|
||||
if p.is_file()
|
||||
]
|
||||
for path in sorted(sources):
|
||||
if path.name.endswith("SpanNames.h") or is_test_path(path):
|
||||
continue
|
||||
text = read_source(path)
|
||||
rel = path.relative_to(root)
|
||||
for call, arglist, lineno in iter_calls(text):
|
||||
positions = CONSTANT_ARG_POSITIONS.get(call, set())
|
||||
args = split_top_level_args(arglist)
|
||||
for idx in positions:
|
||||
if idx >= len(args):
|
||||
continue
|
||||
arg = args[idx]
|
||||
lit = STRING_LITERAL.search(arg)
|
||||
if lit:
|
||||
found_f = True
|
||||
report.violation(
|
||||
"F",
|
||||
f"{rel}:{lineno}",
|
||||
f'{call} arg{idx} "{lit.group(1)}"',
|
||||
"use a *SpanNames.h constant",
|
||||
)
|
||||
continue
|
||||
# Not a literal: Rule H warns when a NAMESPACE-QUALIFIED constant
|
||||
# reference (e.g. `consensus::span::accept`) is not defined in
|
||||
# any *SpanNames.h — i.e. the constant was defined in-place
|
||||
# instead of in the proper header. We only consider qualified
|
||||
# refs (containing `::`): a bare lowercase identifier is almost
|
||||
# always a legitimately dynamic local (a computed span-name leaf
|
||||
# or attribute value), not a misplaced constant, so warning on it
|
||||
# would be noise. Standard-library types (std::...) are skipped.
|
||||
ident = IDENTIFIER_ARG.match(arg)
|
||||
if not (ident and header_symbols):
|
||||
continue
|
||||
ref = ident.group(1)
|
||||
if "::" not in ref or ref.startswith("std::"):
|
||||
continue
|
||||
leaf = ref.split("::")[-1]
|
||||
if leaf not in header_symbols:
|
||||
report.warning(
|
||||
"H",
|
||||
f"{rel}:{lineno}",
|
||||
f"{call} arg{idx} {ref}",
|
||||
"not defined in any *SpanNames.h",
|
||||
)
|
||||
if not found_f:
|
||||
report.ok("F: no string-literal keys/names at telemetry call-sites")
|
||||
|
||||
|
||||
def iter_calls(text: str):
|
||||
"""Yield (call_name, raw_arglist, lineno) for each setAttribute/addEvent/
|
||||
span/childSpan invocation, spanning multiple physical lines if needed."""
|
||||
for m in CALLSITE.finditer(text):
|
||||
name = m.group(1)
|
||||
# Walk from the opening paren, balancing nesting to find the close.
|
||||
# Parens inside a "string literal" are ignored so a value such as
|
||||
# `setAttribute(k, ")")` does not close the call early.
|
||||
i = m.end() # one char past the '('
|
||||
depth = 1
|
||||
in_str = False
|
||||
escaped = False
|
||||
while i < len(text) and depth > 0:
|
||||
c = text[i]
|
||||
if in_str:
|
||||
if escaped:
|
||||
escaped = False
|
||||
elif c == "\\":
|
||||
escaped = True
|
||||
elif c == '"':
|
||||
in_str = False
|
||||
elif c == '"':
|
||||
in_str = True
|
||||
elif c == "(":
|
||||
depth += 1
|
||||
elif c == ")":
|
||||
depth -= 1
|
||||
i += 1
|
||||
arglist = text[m.end() : i - 1]
|
||||
lineno = text.count("\n", 0, m.start()) + 1
|
||||
yield name, arglist, lineno
|
||||
|
||||
|
||||
def run_rule_b_collector(root: Path, l1_keys: Set[str], report: Report) -> None:
|
||||
path = root / "docker" / "telemetry" / "otel-collector-config.yaml"
|
||||
if not path.is_file():
|
||||
report.skip("B", "collector config not present")
|
||||
return
|
||||
text = read_source(path)
|
||||
if "spanmetrics" not in text:
|
||||
report.skip("B", "no spanmetrics block in collector config")
|
||||
return
|
||||
dims = extract_spanmetrics_dimensions(text)
|
||||
if not l1_keys:
|
||||
report.skip("B", "no L1 key set to validate against")
|
||||
return
|
||||
miss = [d for d in dims if d not in l1_keys]
|
||||
for d in miss:
|
||||
report.violation("B", str(path.relative_to(root)), d, "must exist in L1")
|
||||
if not miss:
|
||||
report.ok(f"B: {len(dims)} collector dimension(s) all in L1")
|
||||
|
||||
|
||||
def extract_spanmetrics_dimensions(text: str) -> List[str]:
|
||||
dims: List[str] = []
|
||||
in_dims = False
|
||||
for line in text.splitlines():
|
||||
if re.search(r"\bdimensions\s*:", line):
|
||||
in_dims = True
|
||||
continue
|
||||
if in_dims:
|
||||
m = re.search(r"-\s*name\s*:\s*([A-Za-z0-9_.]+)", line)
|
||||
if m:
|
||||
dims.append(m.group(1))
|
||||
elif line.strip() and not line.lstrip().startswith("-") and ":" in line:
|
||||
in_dims = False
|
||||
return dims
|
||||
|
||||
|
||||
def run_rule_c_tempo(root: Path, l1_keys: Set[str], report: Report) -> None:
|
||||
# The trace-search filter tags live in the Grafana Tempo DATASOURCE
|
||||
# provisioning file (search.filters[].{tag,scope}); the Tempo server
|
||||
# tempo.yaml has no such tags. Prefer the datasource file; fall back to the
|
||||
# server file so the rule still does something if the layout changes.
|
||||
candidates = [
|
||||
root / "docker/telemetry/grafana/provisioning/datasources/tempo.yaml",
|
||||
root / "docker/telemetry/tempo.yaml",
|
||||
]
|
||||
path = next((p for p in candidates if p.is_file()), None)
|
||||
if path is None:
|
||||
report.skip("C", "tempo datasource provisioning not present")
|
||||
return
|
||||
if not l1_keys:
|
||||
report.skip("C", "no L1 key set to validate against")
|
||||
return
|
||||
# Pair each filter's `tag:` with its `scope:` (a few lines below it) and
|
||||
# validate only span-scope tags — resource/intrinsic tags (service.*, name,
|
||||
# status, duration) are not span attributes. Strip a TraceQL span. prefix.
|
||||
lines = read_source(path).splitlines()
|
||||
span_tags: List[str] = []
|
||||
for i, line in enumerate(lines):
|
||||
m = re.search(r"^\s*tag:\s*(\S+)", line)
|
||||
if not m:
|
||||
continue
|
||||
scope = next(
|
||||
(
|
||||
sm.group(1)
|
||||
for j in range(i, min(i + 4, len(lines)))
|
||||
for sm in [re.search(r"scope:\s*(\S+)", lines[j])]
|
||||
if sm
|
||||
),
|
||||
"",
|
||||
)
|
||||
if scope == "span":
|
||||
span_tags.append(TRACEQL_SCOPE.sub("", m.group(1)))
|
||||
if not span_tags:
|
||||
report.skip("C", "no span-scope filter tags in tempo datasource")
|
||||
return
|
||||
miss = [t for t in span_tags if t not in l1_keys]
|
||||
for t in sorted(set(miss)):
|
||||
report.violation("C", str(path.relative_to(root)), t, "must exist in L1")
|
||||
if not miss:
|
||||
report.ok(f"C: {len(span_tags)} tempo span-filter tag(s) all in L1")
|
||||
|
||||
|
||||
def metric_label_names(root: Path) -> Set[str]:
|
||||
"""L6: OTel native-metric label keys emitted by the telemetry code, e.g.
|
||||
`counter->Add(1, {{"job_type", value}})` in MetricsRegistry.cpp. These are
|
||||
a valid source of dashboard labels distinct from span attributes (L1)."""
|
||||
labels: Set[str] = set()
|
||||
for base in ("src", "include"):
|
||||
for p in (root / base).rglob("*.cpp"):
|
||||
if not p.is_file():
|
||||
continue
|
||||
text = read_source(p)
|
||||
if "MetricsRegistry" not in p.name and "metric" not in text.lower():
|
||||
continue
|
||||
labels |= set(METRIC_LABEL.findall(text))
|
||||
return labels
|
||||
|
||||
|
||||
def run_rule_d_dashboards(
|
||||
root: Path, l1_keys: Set[str], metric_labels: Set[str], report: Report
|
||||
) -> None:
|
||||
dash_dir = root / "docker" / "telemetry" / "grafana" / "dashboards"
|
||||
files = sorted(dash_dir.glob("*.json")) if dash_dir.is_dir() else []
|
||||
if not files:
|
||||
report.skip("D", "no dashboard JSON present")
|
||||
return
|
||||
if not l1_keys:
|
||||
report.skip("D", "no L1 key set to validate against")
|
||||
return
|
||||
builtins = {
|
||||
"__name__", # Prometheus reserved label for the metric name itself
|
||||
"le",
|
||||
"exported_instance",
|
||||
"span_name",
|
||||
"status_code",
|
||||
"service_name",
|
||||
"service_version",
|
||||
"service_instance_id",
|
||||
"job",
|
||||
"instance",
|
||||
}
|
||||
# A dashboard label is valid if it is a span attribute (L1), a native-metric
|
||||
# label (L6), or a Prometheus/Grafana builtin.
|
||||
valid = l1_keys | metric_labels | builtins
|
||||
found = False
|
||||
for f in files:
|
||||
try:
|
||||
text = read_source(f)
|
||||
except OSError:
|
||||
continue
|
||||
# PromQL `sum by (a, b)` and `{label="..."}` references.
|
||||
labels: Set[str] = set()
|
||||
for m in re.finditer(r"by\s*\(([^)]*)\)", text):
|
||||
labels |= {x.strip() for x in m.group(1).split(",") if x.strip()}
|
||||
for m in re.finditer(r"\b([a-z_][a-z0-9_.]*)\s*[=!]~?\s*\"", text):
|
||||
labels.add(m.group(1))
|
||||
for lbl in sorted(labels):
|
||||
# Strip a TraceQL scope prefix (span./resource./...) — the bare
|
||||
# attribute is what must resolve against L1.
|
||||
bare = TRACEQL_SCOPE.sub("", lbl)
|
||||
if bare in valid:
|
||||
continue
|
||||
found = True
|
||||
report.violation(
|
||||
"D",
|
||||
str(f.relative_to(root)),
|
||||
lbl,
|
||||
"must exist in L1, a metric label, or be a builtin",
|
||||
)
|
||||
if not found:
|
||||
report.ok(f"D: dashboard PromQL labels all resolve ({len(files)} file(s))")
|
||||
|
||||
|
||||
def run_rule_e_runbook(root: Path, l1_keys: Set[str], report: Report) -> None:
|
||||
path = root / "docs" / "telemetry-runbook.md"
|
||||
if not path.is_file():
|
||||
report.skip("E", "runbook not present")
|
||||
return
|
||||
if not l1_keys:
|
||||
report.skip("E", "no L1 key set to validate against")
|
||||
return
|
||||
text = read_source(path)
|
||||
found = False
|
||||
# Only the dotted `xrpl.<domain>.<field>` attribute form is a violation. The
|
||||
# `xrpl.`-with-trailing-dot anchor is the discriminator: it matches the old
|
||||
# dotted attribute convention being migrated away from, while everything
|
||||
# else legitimately dotted in the runbook does NOT match it —
|
||||
# * span names (`consensus.round`, `tx.process`) no `xrpl.` prefix
|
||||
# * filenames (`xrpld.cfg`, `RCLConsensus.cpp`) `xrpld.`/`.cpp`, not `xrpl.`
|
||||
# * OTel-standard (`service.name`, `http.method`) no `xrpl.` prefix
|
||||
# * metric labels (`xrpl_rpc_command`) underscore, no dot
|
||||
# Legitimate dotted resource attrs (`xrpl.network.id`/`.type`) are in L1 and
|
||||
# are skipped. A dotted `xrpl.` token absent from L1 is a genuine doc/code
|
||||
# mismatch (e.g. `xrpl.tx.hash` where the code emits `tx_hash`).
|
||||
for m in re.finditer(r"`(xrpl\.[a-z][a-z0-9_.]*)`", text):
|
||||
token = m.group(1)
|
||||
if token in l1_keys: # legitimate dotted resource attr (xrpl.network.*)
|
||||
continue
|
||||
found = True
|
||||
report.violation(
|
||||
"E", str(path.relative_to(root)), token, "underscore, not dotted"
|
||||
)
|
||||
if not found:
|
||||
report.ok("E: runbook attribute references consistent with L1")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
864
.github/scripts/otel-naming/test_check_otel_naming.py
vendored
Normal file
864
.github/scripts/otel-naming/test_check_otel_naming.py
vendored
Normal file
@@ -0,0 +1,864 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Unit tests for check_otel_naming.py.
|
||||
|
||||
Stdlib-only (unittest), matching the dependency-free policy of the check itself.
|
||||
Run from anywhere:
|
||||
|
||||
python .github/scripts/otel-naming/test_check_otel_naming.py
|
||||
|
||||
Each rule is exercised in isolation against a synthetic tree / synthetic L1 key
|
||||
set, covering positive (must flag), negative (must not flag), and boundary
|
||||
cases. Rule E (runbook dotted-attribute detection) has the densest coverage
|
||||
because its discriminator — the `xrpl.<domain>.` prefix vs span names,
|
||||
filenames, OTel-standard keys, and metric labels — is the subtlest.
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import importlib.util
|
||||
import io
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
# Load the check module by path (it is not an importable package).
|
||||
_spec = importlib.util.spec_from_file_location(
|
||||
"check_otel_naming", str(Path(__file__).with_name("check_otel_naming.py"))
|
||||
)
|
||||
chk = importlib.util.module_from_spec(_spec)
|
||||
_spec.loader.exec_module(chk)
|
||||
|
||||
|
||||
# A controlled L1 set used across tests: the two legitimate dotted resource
|
||||
# attrs plus a handful of underscore span-attribute keys.
|
||||
L1 = {
|
||||
"xrpl.network.id",
|
||||
"xrpl.network.type",
|
||||
"tx_hash",
|
||||
"peer_id",
|
||||
"consensus_mode",
|
||||
"command",
|
||||
"rpc_status",
|
||||
"ledger_seq",
|
||||
}
|
||||
|
||||
|
||||
def _run_rule_e(runbook_text: str):
|
||||
"""Run Rule E against a synthetic runbook; return the flagged tokens."""
|
||||
d = Path(tempfile.mkdtemp())
|
||||
try:
|
||||
(d / "docs").mkdir()
|
||||
(d / "docs" / "telemetry-runbook.md").write_text(runbook_text)
|
||||
report = chk.Report()
|
||||
chk.run_rule_e_runbook(d, set(L1), report)
|
||||
return sorted(v[2] for v in report.violations)
|
||||
finally:
|
||||
shutil.rmtree(d)
|
||||
|
||||
|
||||
class RuleERunbook(unittest.TestCase):
|
||||
"""Rule E: only dotted `xrpl.<domain>.<field>` attribute keys are flagged."""
|
||||
|
||||
# ----- positive: genuine dotted attribute-key violations -----
|
||||
def test_single_dotted_attr(self):
|
||||
self.assertEqual(_run_rule_e("`xrpl.tx.hash`"), ["xrpl.tx.hash"])
|
||||
|
||||
def test_multiple_dotted_attrs(self):
|
||||
self.assertEqual(
|
||||
_run_rule_e("`xrpl.tx.hash` and `xrpl.consensus.mode`"),
|
||||
["xrpl.consensus.mode", "xrpl.tx.hash"],
|
||||
)
|
||||
|
||||
def test_deep_dotted_three_segments(self):
|
||||
self.assertEqual(
|
||||
_run_rule_e("`xrpl.consensus.ledger.seq`"), ["xrpl.consensus.ledger.seq"]
|
||||
)
|
||||
|
||||
def test_dotted_attr_with_underscore_field(self):
|
||||
self.assertEqual(
|
||||
_run_rule_e("`xrpl.consensus.round_id`"), ["xrpl.consensus.round_id"]
|
||||
)
|
||||
|
||||
def test_repeated_token_reported_each_occurrence(self):
|
||||
self.assertEqual(
|
||||
_run_rule_e("`xrpl.tx.hash` ... `xrpl.tx.hash`"),
|
||||
["xrpl.tx.hash", "xrpl.tx.hash"],
|
||||
)
|
||||
|
||||
def test_resource_attr_not_in_l1_is_flagged(self):
|
||||
self.assertEqual(
|
||||
_run_rule_e("`xrpl.network.unknown`"), ["xrpl.network.unknown"]
|
||||
)
|
||||
|
||||
# ----- negative: legitimately-dotted tokens that must NOT be flagged -----
|
||||
def test_span_name_single(self):
|
||||
self.assertEqual(_run_rule_e("`consensus.round`"), [])
|
||||
|
||||
def test_span_name_multi_segment(self):
|
||||
self.assertEqual(
|
||||
_run_rule_e("`consensus.phase.open` `rpc.command.server_info`"), []
|
||||
)
|
||||
|
||||
def test_filename_cfg(self):
|
||||
self.assertEqual(_run_rule_e("`xrpld.cfg`"), [])
|
||||
|
||||
def test_filename_cpp(self):
|
||||
self.assertEqual(_run_rule_e("`RCLConsensus.cpp`"), [])
|
||||
|
||||
def test_otel_standard_service_name(self):
|
||||
self.assertEqual(_run_rule_e("`service.name`"), [])
|
||||
|
||||
def test_otel_standard_http_method(self):
|
||||
self.assertEqual(_run_rule_e("`http.method`"), [])
|
||||
|
||||
def test_metric_label_underscore(self):
|
||||
self.assertEqual(_run_rule_e("`xrpl_rpc_command`"), [])
|
||||
|
||||
def test_bare_underscore_attrs(self):
|
||||
self.assertEqual(_run_rule_e("`tx_hash` `consensus_mode`"), [])
|
||||
|
||||
def test_legit_dotted_resource_attrs_in_l1(self):
|
||||
self.assertEqual(_run_rule_e("`xrpl.network.id` `xrpl.network.type`"), [])
|
||||
|
||||
def test_prose_word(self):
|
||||
self.assertEqual(_run_rule_e("the `command` attribute"), [])
|
||||
|
||||
def test_plain_prose_no_backticks(self):
|
||||
self.assertEqual(_run_rule_e("xrpl.tx.hash without backticks is prose"), [])
|
||||
|
||||
# ----- boundary -----
|
||||
def test_empty_runbook(self):
|
||||
self.assertEqual(_run_rule_e(""), [])
|
||||
|
||||
def test_lookalike_prefix_xrpld(self):
|
||||
# `xrpld.` is NOT `xrpl.` — must not match.
|
||||
self.assertEqual(_run_rule_e("`xrpld.foo`"), [])
|
||||
|
||||
def test_lookalike_prefix_underscore(self):
|
||||
# `xrpl_rpc.command` starts with `xrpl_`, not `xrpl.`.
|
||||
self.assertEqual(_run_rule_e("`xrpl_rpc.command`"), [])
|
||||
|
||||
def test_uppercase_segment_not_matched(self):
|
||||
# The pattern requires a lowercase char after `xrpl.`; uppercase keys are
|
||||
# caught by Rule G at the L1 layer, not by the runbook text scan.
|
||||
self.assertEqual(_run_rule_e("`xrpl.TX.hash`"), [])
|
||||
|
||||
def test_token_touching_table_pipes(self):
|
||||
self.assertEqual(_run_rule_e("| `xrpl.tx.hash` | desc |"), ["xrpl.tx.hash"])
|
||||
|
||||
def test_mixed_line_only_xrpl_dotted_flagged(self):
|
||||
self.assertEqual(
|
||||
_run_rule_e("`consensus.round` uses `xrpl.tx.hash` and `service.name`"),
|
||||
["xrpl.tx.hash"],
|
||||
)
|
||||
|
||||
def test_skips_when_runbook_absent(self):
|
||||
d = Path(tempfile.mkdtemp())
|
||||
try:
|
||||
report = chk.Report()
|
||||
chk.run_rule_e_runbook(d, set(L1), report)
|
||||
self.assertEqual(report.violations, [])
|
||||
self.assertTrue(any("SKIP: E" in s for s in report.skips))
|
||||
finally:
|
||||
shutil.rmtree(d)
|
||||
|
||||
def test_skips_when_l1_empty(self):
|
||||
d = Path(tempfile.mkdtemp())
|
||||
try:
|
||||
(d / "docs").mkdir()
|
||||
(d / "docs" / "telemetry-runbook.md").write_text("`xrpl.tx.hash`")
|
||||
report = chk.Report()
|
||||
chk.run_rule_e_runbook(d, set(), report)
|
||||
self.assertEqual(report.violations, [])
|
||||
self.assertTrue(any("SKIP: E" in s for s in report.skips))
|
||||
finally:
|
||||
shutil.rmtree(d)
|
||||
|
||||
|
||||
class DslParser(unittest.TestCase):
|
||||
"""The makeStr/join/seg:: constexpr DSL resolver — the foundation of the
|
||||
L1 key set. Covers flat, nested, cross-file, alias, and multi-line forms."""
|
||||
|
||||
def test_flat_join(self):
|
||||
syms = chk.resolve_constants(
|
||||
'inline constexpr auto a = makeStr("xrpl");\n'
|
||||
'inline constexpr auto b = makeStr("network");\n'
|
||||
"inline constexpr auto c = join(a, b);\n"
|
||||
)
|
||||
self.assertEqual(syms["c"], "xrpl.network")
|
||||
|
||||
def test_nested_join_three_segments(self):
|
||||
syms = chk.resolve_constants(
|
||||
'inline constexpr auto xrpl = makeStr("xrpl");\n'
|
||||
'inline constexpr auto network = makeStr("network");\n'
|
||||
"inline constexpr auto networkId = "
|
||||
'join(join(xrpl, network), makeStr("id"));\n'
|
||||
)
|
||||
self.assertEqual(syms["networkId"], "xrpl.network.id")
|
||||
|
||||
def test_qualified_seg_reference(self):
|
||||
# `seg::rpc` resolves by its bare leaf `rpc`.
|
||||
syms = chk.resolve_constants('inline constexpr auto rpc = makeStr("rpc");\n')
|
||||
syms2 = chk.resolve_constants(
|
||||
'inline constexpr auto command = join(seg::rpc, makeStr("command"));\n',
|
||||
syms,
|
||||
)
|
||||
self.assertEqual(syms2["command"], "rpc.command")
|
||||
|
||||
def test_alias_reference(self):
|
||||
syms = chk.resolve_constants('inline constexpr auto rpc = makeStr("rpc");\n')
|
||||
chk.resolve_constants("inline constexpr auto alias = seg::rpc;\n", syms)
|
||||
self.assertEqual(syms["alias"], "rpc")
|
||||
|
||||
def test_unresolvable_expr_omitted(self):
|
||||
syms = chk.resolve_constants("inline constexpr auto x = join(unknown, y);\n")
|
||||
self.assertNotIn("x", syms)
|
||||
|
||||
def test_split_top_level_args_respects_nesting(self):
|
||||
self.assertEqual(
|
||||
chk.split_top_level_args("join(seg::a, b), c"),
|
||||
["join(seg::a, b)", " c"],
|
||||
)
|
||||
|
||||
def test_split_top_level_args_ignores_comma_in_string(self):
|
||||
self.assertEqual(
|
||||
chk.split_top_level_args('key, ","'),
|
||||
["key", ' ","'],
|
||||
)
|
||||
|
||||
def test_strip_comments_removes_line_and_block(self):
|
||||
self.assertEqual(
|
||||
chk.strip_comments("a // line\nb /* blk */ c").split(),
|
||||
["a", "b", "c"],
|
||||
)
|
||||
|
||||
|
||||
def _write(path: Path, text: str) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(text)
|
||||
|
||||
|
||||
def _header(ns_attr_body: str, prefix_seg: str = "") -> str:
|
||||
"""A minimal *SpanNames.h body: optional seg defs + a namespace attr block."""
|
||||
return (
|
||||
"#pragma once\n"
|
||||
+ prefix_seg
|
||||
+ "namespace xrpl::telemetry::demo::span {\n"
|
||||
+ "namespace attr {\n"
|
||||
+ ns_attr_body
|
||||
+ "} // namespace attr\n"
|
||||
+ "}\n"
|
||||
)
|
||||
|
||||
|
||||
class AttrKeyExtraction(unittest.TestCase):
|
||||
"""attr_keys_from_header: comment-stripping + multi-line + using re-export."""
|
||||
|
||||
def _l1(self, header_text):
|
||||
d = Path(tempfile.mkdtemp())
|
||||
try:
|
||||
h = d / "src" / "DemoSpanNames.h"
|
||||
_write(h, header_text)
|
||||
syms = chk.build_global_symbols([h])
|
||||
return chk.attr_keys_from_header(h, syms)
|
||||
finally:
|
||||
shutil.rmtree(d)
|
||||
|
||||
def test_single_line_makestr(self):
|
||||
keys = self._l1(_header('inline constexpr auto k = makeStr("tx_hash");\n'))
|
||||
self.assertIn("tx_hash", keys)
|
||||
|
||||
def test_multiline_constexpr_captured(self):
|
||||
keys = self._l1(
|
||||
_header("inline constexpr auto k =\n" ' makeStr("round_time_ms");\n')
|
||||
)
|
||||
self.assertIn("round_time_ms", keys)
|
||||
|
||||
def test_commented_makestr_not_leaked(self):
|
||||
keys = self._l1(
|
||||
_header(
|
||||
'inline constexpr auto k = makeStr("good");\n'
|
||||
'// inline constexpr auto bad = makeStr("old.dotted");\n'
|
||||
)
|
||||
)
|
||||
self.assertIn("good", keys)
|
||||
self.assertNotIn("old.dotted", keys)
|
||||
|
||||
def test_block_commented_makestr_not_leaked(self):
|
||||
keys = self._l1(
|
||||
_header(
|
||||
'inline constexpr auto k = makeStr("good");\n'
|
||||
'/* makeStr("blockbad") */\n'
|
||||
)
|
||||
)
|
||||
self.assertNotIn("blockbad", keys)
|
||||
|
||||
|
||||
class CamelToDotSegments(unittest.TestCase):
|
||||
"""semconv CamelCase -> dotted OTel-standard key derivation."""
|
||||
|
||||
def test_service_instance_id(self):
|
||||
self.assertEqual(
|
||||
chk.camel_to_dotsegments("ServiceInstanceId"),
|
||||
["service", "instance", "id"],
|
||||
)
|
||||
|
||||
def test_service_name(self):
|
||||
self.assertEqual(chk.camel_to_dotsegments("ServiceName"), ["service", "name"])
|
||||
|
||||
def test_derive_keys_from_telemetry_cpp(self):
|
||||
d = Path(tempfile.mkdtemp())
|
||||
try:
|
||||
tele = d / "src" / "libxrpl" / "telemetry" / "Telemetry.cpp"
|
||||
_write(
|
||||
tele,
|
||||
"resource::Resource::Create({\n"
|
||||
" {semconv::service::kServiceName, x},\n"
|
||||
" {semconv::service::kServiceInstanceId, y},\n"
|
||||
"});\n",
|
||||
)
|
||||
report = chk.Report()
|
||||
allow = chk.derive_dotted_resource_keys(d, {}, report)
|
||||
self.assertIn("service.name", allow)
|
||||
self.assertIn("service.instance.id", allow)
|
||||
finally:
|
||||
shutil.rmtree(d)
|
||||
|
||||
|
||||
class SymbolCollision(unittest.TestCase):
|
||||
"""attr_keys_from_header must resolve a constant against ITS OWN header, so
|
||||
two headers defining a same-named constant each report their real wire key.
|
||||
Regression for the flat-symbol-table collision that let a later header
|
||||
clobber an earlier one and erased a dotted key from L1 (a Rule-A blind
|
||||
spot)."""
|
||||
|
||||
def _build(self, files):
|
||||
d = Path(tempfile.mkdtemp())
|
||||
paths = {}
|
||||
for rel, text in files.items():
|
||||
p = d / rel
|
||||
_write(p, text)
|
||||
paths[rel] = p
|
||||
return d, paths
|
||||
|
||||
def test_same_named_const_not_clobbered_across_headers(self):
|
||||
base = (
|
||||
"#pragma once\n"
|
||||
"namespace xrpl::telemetry {\n"
|
||||
'namespace seg { inline constexpr auto xrpl = makeStr("xrpl");\n'
|
||||
'inline constexpr auto ledger = makeStr("ledger"); }\n'
|
||||
"namespace attr {\n"
|
||||
"inline constexpr auto ledgerHash = "
|
||||
'join(join(seg::xrpl, seg::ledger), makeStr("hash"));\n'
|
||||
"}\n}\n"
|
||||
)
|
||||
cons = (
|
||||
"#pragma once\n"
|
||||
"namespace xrpl::telemetry::consensus::span {\n"
|
||||
"namespace attr { inline constexpr auto ledgerHash = "
|
||||
'makeStr("ledger_hash"); }\n}\n'
|
||||
)
|
||||
d, paths = self._build(
|
||||
{
|
||||
"include/xrpl/telemetry/SpanNames.h": base,
|
||||
"src/xrpld/consensus/ConsensusSpanNames.h": cons,
|
||||
}
|
||||
)
|
||||
try:
|
||||
headers = chk.find_spanname_headers(d)
|
||||
syms = chk.build_global_symbols(headers)
|
||||
by_name = {p.name: chk.attr_keys_from_header(p, syms) for p in headers}
|
||||
# The base header keeps its dotted key; consensus keeps the bare one.
|
||||
self.assertIn("xrpl.ledger.hash", by_name["SpanNames.h"])
|
||||
self.assertEqual(by_name["ConsensusSpanNames.h"], {"ledger_hash"})
|
||||
finally:
|
||||
shutil.rmtree(d)
|
||||
|
||||
def test_using_reexport_still_resolves_globally(self):
|
||||
# A `using`-re-export imports a constant defined elsewhere; it must
|
||||
# resolve against the global table, not the local header.
|
||||
base = (
|
||||
"#pragma once\n"
|
||||
"namespace xrpl::telemetry {\n"
|
||||
"namespace attr { inline constexpr auto txHash = "
|
||||
'makeStr("tx_hash"); }\n}\n'
|
||||
)
|
||||
dom = (
|
||||
"#pragma once\n"
|
||||
"namespace xrpl::telemetry::tx::span {\n"
|
||||
"namespace attr { using ::xrpl::telemetry::attr::txHash; }\n}\n"
|
||||
)
|
||||
d, paths = self._build(
|
||||
{
|
||||
"include/xrpl/telemetry/SpanNames.h": base,
|
||||
"src/xrpld/app/misc/TxSpanNames.h": dom,
|
||||
}
|
||||
)
|
||||
try:
|
||||
headers = chk.find_spanname_headers(d)
|
||||
syms = chk.build_global_symbols(headers)
|
||||
keys = chk.attr_keys_from_header(
|
||||
paths["src/xrpld/app/misc/TxSpanNames.h"], syms
|
||||
)
|
||||
self.assertEqual(keys, {"tx_hash"})
|
||||
finally:
|
||||
shutil.rmtree(d)
|
||||
|
||||
|
||||
class ResourceAllowlistScope(unittest.TestCase):
|
||||
"""derive_dotted_resource_keys must allowlist ONLY the dotted keys actually
|
||||
passed to Resource::Create() — not every dotted key in the base header. A
|
||||
dotted attr declared in a header but not set as a resource attr is a Rule-A
|
||||
violation."""
|
||||
|
||||
def _derive(self, tele_text, span_text):
|
||||
d = Path(tempfile.mkdtemp())
|
||||
try:
|
||||
_write(d / "src" / "libxrpl" / "telemetry" / "Telemetry.cpp", tele_text)
|
||||
_write(d / "include" / "xrpl" / "telemetry" / "SpanNames.h", span_text)
|
||||
headers = chk.find_spanname_headers(d)
|
||||
syms = chk.build_global_symbols(headers)
|
||||
allow = chk.derive_dotted_resource_keys(d, syms, chk.Report())
|
||||
return allow, syms, headers, d
|
||||
except Exception:
|
||||
shutil.rmtree(d)
|
||||
raise
|
||||
|
||||
def test_dotted_span_attr_not_allowlisted_and_flagged(self):
|
||||
span = (
|
||||
"#pragma once\n"
|
||||
"namespace xrpl::telemetry {\n"
|
||||
'namespace seg { inline constexpr auto xrpl = makeStr("xrpl");\n'
|
||||
'inline constexpr auto ledger = makeStr("ledger");\n'
|
||||
'inline constexpr auto network = makeStr("network"); }\n'
|
||||
"namespace attr {\n"
|
||||
"inline constexpr auto networkId = "
|
||||
'join(join(seg::xrpl, seg::network), makeStr("id"));\n'
|
||||
"inline constexpr auto ledgerHash = "
|
||||
'join(join(seg::xrpl, seg::ledger), makeStr("hash"));\n'
|
||||
"}\n}\n"
|
||||
)
|
||||
tele = (
|
||||
"auto r = resource::Resource::Create({\n"
|
||||
" {semconv::service::kServiceName, x},\n"
|
||||
" {std::string(attr::networkId), n},\n"
|
||||
"});\n"
|
||||
)
|
||||
allow, syms, headers, d = self._derive(tele, span)
|
||||
try:
|
||||
# networkId IS a resource attr; ledgerHash is NOT, despite living in
|
||||
# the base header.
|
||||
self.assertIn("xrpl.network.id", allow)
|
||||
self.assertNotIn("xrpl.ledger.hash", allow)
|
||||
kbh = {h: chk.attr_keys_from_header(h, syms) for h in headers}
|
||||
report = chk.Report()
|
||||
chk.run_rule_a(kbh, allow, report)
|
||||
self.assertEqual([v[2] for v in report.violations], ["xrpl.ledger.hash"])
|
||||
finally:
|
||||
shutil.rmtree(d)
|
||||
|
||||
def test_resource_block_brace_matched(self):
|
||||
# A nested {key,value} initializer must not truncate the block scan.
|
||||
tele = (
|
||||
"auto r = resource::Resource::Create({\n"
|
||||
" {semconv::service::kServiceName, x},\n"
|
||||
" {std::string(attr::networkType), t},\n"
|
||||
"});\n"
|
||||
)
|
||||
span = (
|
||||
"#pragma once\n"
|
||||
"namespace xrpl::telemetry {\n"
|
||||
'namespace seg { inline constexpr auto xrpl = makeStr("xrpl");\n'
|
||||
'inline constexpr auto network = makeStr("network"); }\n'
|
||||
"namespace attr { inline constexpr auto networkType = "
|
||||
'join(join(seg::xrpl, seg::network), makeStr("type")); }\n}\n'
|
||||
)
|
||||
allow, _syms, _headers, d = self._derive(tele, span)
|
||||
try:
|
||||
self.assertIn("xrpl.network.type", allow)
|
||||
self.assertIn("service.name", allow)
|
||||
finally:
|
||||
shutil.rmtree(d)
|
||||
|
||||
|
||||
def _run_rule_a(keys_by_header, allow):
|
||||
report = chk.Report()
|
||||
chk.run_rule_a(keys_by_header, allow, report)
|
||||
return sorted(v[2] for v in report.violations)
|
||||
|
||||
|
||||
class RuleADotted(unittest.TestCase):
|
||||
def test_dotted_attr_not_in_allow_flagged(self):
|
||||
kbh = {Path("src/RpcSpanNames.h"): {"xrpl.tx.hash", "command"}}
|
||||
self.assertEqual(_run_rule_a(kbh, {"xrpl.network.id"}), ["xrpl.tx.hash"])
|
||||
|
||||
def test_resource_attr_in_allow_passes(self):
|
||||
kbh = {Path("src/SpanNames.h"): {"xrpl.network.id"}}
|
||||
self.assertEqual(_run_rule_a(kbh, {"xrpl.network.id"}), [])
|
||||
|
||||
def test_bare_key_never_flagged(self):
|
||||
kbh = {Path("src/TxSpanNames.h"): {"tx_hash", "command"}}
|
||||
self.assertEqual(_run_rule_a(kbh, set()), [])
|
||||
|
||||
|
||||
def _run_rule_g(keys_by_header):
|
||||
report = chk.Report()
|
||||
chk.run_rule_g(keys_by_header, report)
|
||||
return sorted(v[2] for v in report.violations)
|
||||
|
||||
|
||||
class RuleGSnakeCase(unittest.TestCase):
|
||||
def test_camelcase_flagged(self):
|
||||
self.assertEqual(_run_rule_g({Path("h"): {"txHash"}}), ["txHash"])
|
||||
|
||||
def test_uppercase_flagged(self):
|
||||
self.assertEqual(_run_rule_g({Path("h"): {"TX_HASH"}}), ["TX_HASH"])
|
||||
|
||||
def test_space_flagged(self):
|
||||
self.assertEqual(_run_rule_g({Path("h"): {"bad key"}}), ["bad key"])
|
||||
|
||||
def test_snake_case_passes(self):
|
||||
self.assertEqual(_run_rule_g({Path("h"): {"tx_hash", "rpc_status"}}), [])
|
||||
|
||||
def test_dotted_resource_segments_pass(self):
|
||||
self.assertEqual(_run_rule_g({Path("h"): {"xrpl.network.id"}}), [])
|
||||
|
||||
def test_dotted_with_bad_segment_flagged(self):
|
||||
self.assertEqual(
|
||||
_run_rule_g({Path("h"): {"xrpl.Network.id"}}), ["xrpl.Network.id"]
|
||||
)
|
||||
|
||||
|
||||
class RuleFAndH(unittest.TestCase):
|
||||
"""run_rule_f: literal keys/span-names flagged; values & tests exempt.
|
||||
Rule H: qualified constant not in any header warns (non-fatal)."""
|
||||
|
||||
def _run(self, rel_path, source, header_symbols=frozenset()):
|
||||
d = Path(tempfile.mkdtemp())
|
||||
try:
|
||||
_write(d / rel_path, source)
|
||||
report = chk.Report()
|
||||
chk.run_rule_f(d, report, set(header_symbols))
|
||||
return (
|
||||
sorted(v[2] for v in report.violations),
|
||||
sorted(w[2] for w in report.warnings),
|
||||
)
|
||||
finally:
|
||||
shutil.rmtree(d)
|
||||
|
||||
def test_literal_key_flagged(self):
|
||||
v, _ = self._run("src/Foo.cpp", 'g.setAttribute("lit_key", v);\n')
|
||||
self.assertEqual(v, ['setAttribute arg0 "lit_key"'])
|
||||
|
||||
def test_literal_value_exempt(self):
|
||||
v, _ = self._run("src/Foo.cpp", 'g.setAttribute(attr::command, "submit");\n')
|
||||
self.assertEqual(v, [])
|
||||
|
||||
def test_span_name_args_flagged(self):
|
||||
v, _ = self._run("src/Foo.cpp", 'SpanGuard::span(cat, "rpc", "command");\n')
|
||||
self.assertEqual(v, ['span arg1 "rpc"', 'span arg2 "command"'])
|
||||
|
||||
def test_test_path_exempt(self):
|
||||
v, _ = self._run("src/test/Foo.cpp", 'g.setAttribute("lit_key", v);\n')
|
||||
self.assertEqual(v, [])
|
||||
|
||||
def test_spannames_header_exempt(self):
|
||||
v, _ = self._run("src/DemoSpanNames.h", 'g.setAttribute("lit_key", v);\n')
|
||||
self.assertEqual(v, [])
|
||||
|
||||
def test_bare_span_call_not_matched(self):
|
||||
# No SpanGuard/./-> receiver -> not a telemetry call-site.
|
||||
v, _ = self._run("src/Foo.cpp", 'auto s = span("not", "telemetry");\n')
|
||||
self.assertEqual(v, [])
|
||||
|
||||
def test_multiline_call_reports_first_line(self):
|
||||
v, _ = self._run("src/Foo.cpp", 'g.setAttribute(\n "k",\n v);\n')
|
||||
self.assertEqual(v, ['setAttribute arg0 "k"'])
|
||||
|
||||
def test_paren_in_string_value_does_not_break_parsing(self):
|
||||
# The ")" inside the value must not end the call early; key still seen.
|
||||
v, _ = self._run("src/Foo.cpp", 'g.setAttribute("k", ")");\n')
|
||||
self.assertEqual(v, ['setAttribute arg0 "k"'])
|
||||
|
||||
def test_rule_h_qualified_constant_warns(self):
|
||||
v, w = self._run(
|
||||
"src/Foo.cpp",
|
||||
"g.setAttribute(consensus::span::accept, v);\n",
|
||||
header_symbols={"command"},
|
||||
)
|
||||
self.assertEqual(v, [])
|
||||
self.assertEqual(w, ["setAttribute arg0 consensus::span::accept"])
|
||||
|
||||
def test_rule_h_known_constant_no_warning(self):
|
||||
_, w = self._run(
|
||||
"src/Foo.cpp",
|
||||
"g.setAttribute(rpc_span::attr::command, v);\n",
|
||||
header_symbols={"command"},
|
||||
)
|
||||
self.assertEqual(w, [])
|
||||
|
||||
def test_rule_h_bare_local_no_warning(self):
|
||||
_, w = self._run(
|
||||
"src/Foo.cpp", "g.setAttribute(myLeaf, v);\n", header_symbols={"command"}
|
||||
)
|
||||
self.assertEqual(w, [])
|
||||
|
||||
|
||||
class RuleBCollector(unittest.TestCase):
|
||||
def _run(self, yaml_text, l1):
|
||||
d = Path(tempfile.mkdtemp())
|
||||
try:
|
||||
_write(d / "docker" / "telemetry" / "otel-collector-config.yaml", yaml_text)
|
||||
report = chk.Report()
|
||||
chk.run_rule_b_collector(d, set(l1), report)
|
||||
return sorted(v[2] for v in report.violations), report.skips
|
||||
finally:
|
||||
shutil.rmtree(d)
|
||||
|
||||
def test_dimension_not_in_l1_flagged(self):
|
||||
y = "spanmetrics:\n dimensions:\n - name: bogus_dim\n - name: command\n"
|
||||
v, _ = self._run(y, {"command"})
|
||||
self.assertEqual(v, ["bogus_dim"])
|
||||
|
||||
def test_all_dimensions_in_l1_pass(self):
|
||||
y = "spanmetrics:\n dimensions:\n - name: command\n - name: rpc_status\n"
|
||||
v, _ = self._run(y, {"command", "rpc_status"})
|
||||
self.assertEqual(v, [])
|
||||
|
||||
def test_skip_when_no_spanmetrics_block(self):
|
||||
v, skips = self._run("receivers:\n otlp:\n", {"command"})
|
||||
self.assertEqual(v, [])
|
||||
self.assertTrue(any("SKIP: B" in s for s in skips))
|
||||
|
||||
|
||||
class RuleCTempo(unittest.TestCase):
|
||||
"""Rule C reads the Grafana Tempo DATASOURCE file's search.filters and
|
||||
validates only span-scope tags against L1."""
|
||||
|
||||
DS = "docker/telemetry/grafana/provisioning/datasources/tempo.yaml"
|
||||
|
||||
def _run(self, yaml_text, l1):
|
||||
d = Path(tempfile.mkdtemp())
|
||||
try:
|
||||
_write(d / self.DS, yaml_text)
|
||||
report = chk.Report()
|
||||
chk.run_rule_c_tempo(d, set(l1), report)
|
||||
return sorted(v[2] for v in report.violations), report.skips
|
||||
finally:
|
||||
shutil.rmtree(d)
|
||||
|
||||
def _filter(self, fid, tag, scope):
|
||||
return (
|
||||
f" - id: {fid}\n"
|
||||
f" tag: {tag}\n"
|
||||
f' operator: "="\n'
|
||||
f" scope: {scope}\n"
|
||||
f" type: static\n"
|
||||
)
|
||||
|
||||
def test_span_tag_not_in_l1_flagged(self):
|
||||
y = "search:\n filters:\n" + self._filter("f1", "bogus_tag", "span")
|
||||
v, _ = self._run(y, {"command"})
|
||||
self.assertEqual(v, ["bogus_tag"])
|
||||
|
||||
def test_span_tags_in_l1_pass(self):
|
||||
y = (
|
||||
"search:\n filters:\n"
|
||||
+ self._filter("f1", "command", "span")
|
||||
+ self._filter("f2", "tx_hash", "span")
|
||||
)
|
||||
v, _ = self._run(y, {"command", "tx_hash"})
|
||||
self.assertEqual(v, [])
|
||||
|
||||
def test_resource_and_intrinsic_tags_ignored(self):
|
||||
# service.* (resource) and name/status/duration (intrinsic) are not
|
||||
# span attributes — they must not be validated against L1.
|
||||
y = (
|
||||
"search:\n filters:\n"
|
||||
+ self._filter("f1", "service.instance.id", "resource")
|
||||
+ self._filter("f2", "name", "intrinsic")
|
||||
+ self._filter("f3", "duration", "intrinsic")
|
||||
)
|
||||
v, skips = self._run(y, {"command"})
|
||||
self.assertEqual(v, [])
|
||||
self.assertTrue(any("SKIP: C" in s for s in skips))
|
||||
|
||||
def test_skip_when_datasource_absent(self):
|
||||
d = Path(tempfile.mkdtemp())
|
||||
try:
|
||||
report = chk.Report()
|
||||
chk.run_rule_c_tempo(d, {"command"}, report)
|
||||
self.assertEqual(report.violations, [])
|
||||
self.assertTrue(any("SKIP: C" in s for s in report.skips))
|
||||
finally:
|
||||
shutil.rmtree(d)
|
||||
|
||||
|
||||
class RuleDDashboards(unittest.TestCase):
|
||||
def _run(self, json_text, l1, metric_labels=frozenset()):
|
||||
d = Path(tempfile.mkdtemp())
|
||||
try:
|
||||
_write(
|
||||
d / "docker" / "telemetry" / "grafana" / "dashboards" / "x.json",
|
||||
json_text,
|
||||
)
|
||||
report = chk.Report()
|
||||
chk.run_rule_d_dashboards(d, set(l1), set(metric_labels), report)
|
||||
return sorted(v[2] for v in report.violations)
|
||||
finally:
|
||||
shutil.rmtree(d)
|
||||
|
||||
def test_unknown_promql_label_flagged(self):
|
||||
self.assertEqual(
|
||||
self._run('"expr": "sum by (bogus_label) (x)"', {"command"}),
|
||||
["bogus_label"],
|
||||
)
|
||||
|
||||
def test_builtin_labels_not_flagged(self):
|
||||
self.assertEqual(
|
||||
self._run('"expr": "sum by (le, span_name, exported_instance) (x)"', set()),
|
||||
[],
|
||||
)
|
||||
|
||||
def test_prometheus_name_label_not_flagged(self):
|
||||
# `__name__` is the Prometheus reserved metric-name label; the renamed
|
||||
# system-*.json dashboards use `sum by (le, __name__)`.
|
||||
self.assertEqual(
|
||||
self._run('"expr": "sum by (le, __name__) (rate(x[5m]))"', set()),
|
||||
[],
|
||||
)
|
||||
|
||||
def test_l1_label_passes(self):
|
||||
self.assertEqual(self._run('"q": "{command=\\"x\\"}"', {"command"}), [])
|
||||
|
||||
def test_traceql_span_prefix_stripped(self):
|
||||
# `span.establish_count` must validate against the bare L1 key.
|
||||
self.assertEqual(
|
||||
self._run(
|
||||
'"expr": "count_over_time(x) by (span.establish_count)"',
|
||||
{"establish_count"},
|
||||
),
|
||||
[],
|
||||
)
|
||||
|
||||
def test_traceql_resource_prefix_stripped(self):
|
||||
self.assertEqual(self._run('"q": "{resource.service_name=\\"x\\"}"', set()), [])
|
||||
|
||||
def test_native_metric_label_passes(self):
|
||||
# `job_type` / `reason` are emitted by MetricsRegistry, not span attrs.
|
||||
self.assertEqual(
|
||||
self._run(
|
||||
'"expr": "sum by (job_type, reason) (x)"',
|
||||
{"command"},
|
||||
metric_labels={"job_type", "reason"},
|
||||
),
|
||||
[],
|
||||
)
|
||||
|
||||
def test_unknown_label_still_flagged_with_metric_labels(self):
|
||||
# A label that is neither L1, metric label, nor builtin still fails.
|
||||
self.assertEqual(
|
||||
self._run(
|
||||
'"expr": "sum by (bogus) (x)"',
|
||||
{"command"},
|
||||
metric_labels={"job_type"},
|
||||
),
|
||||
["bogus"],
|
||||
)
|
||||
|
||||
def test_span_prefixed_unknown_still_flagged(self):
|
||||
# `span.not_a_key` whose bare form is unknown is still a violation.
|
||||
self.assertEqual(
|
||||
self._run('"expr": "x by (span.not_a_key)"', {"command"}),
|
||||
["span.not_a_key"],
|
||||
)
|
||||
|
||||
|
||||
class MetricLabelExtraction(unittest.TestCase):
|
||||
"""L6: native-metric label keys parsed from C++ instrument calls."""
|
||||
|
||||
def test_extracts_add_label(self):
|
||||
d = Path(tempfile.mkdtemp())
|
||||
try:
|
||||
_write(
|
||||
d / "src" / "xrpld" / "telemetry" / "MetricsRegistry.cpp",
|
||||
'counter->Add(1, {{"job_type", std::string(jobType)}});\n'
|
||||
'c2->Add(1, {{"reason", std::string(r)}});\n',
|
||||
)
|
||||
self.assertEqual(chk.metric_label_names(d), {"job_type", "reason"})
|
||||
finally:
|
||||
shutil.rmtree(d)
|
||||
|
||||
def test_no_metrics_file_empty(self):
|
||||
d = Path(tempfile.mkdtemp())
|
||||
try:
|
||||
(d / "src").mkdir()
|
||||
self.assertEqual(chk.metric_label_names(d), set())
|
||||
finally:
|
||||
shutil.rmtree(d)
|
||||
|
||||
|
||||
class ReportExitContract(unittest.TestCase):
|
||||
@staticmethod
|
||||
def _exit_code(report):
|
||||
"""Call render_and_exit (which prints + raises SystemExit), swallowing
|
||||
its stdout, and return the exit code."""
|
||||
with contextlib.redirect_stdout(io.StringIO()):
|
||||
try:
|
||||
report.render_and_exit()
|
||||
except SystemExit as e:
|
||||
return e.code
|
||||
return None # pragma: no cover - render_and_exit always exits
|
||||
|
||||
def test_violation_exits_nonzero(self):
|
||||
r = chk.Report()
|
||||
r.violation("A", "f", "tok", "exp")
|
||||
self.assertEqual(self._exit_code(r), 1)
|
||||
|
||||
def test_clean_exits_zero(self):
|
||||
r = chk.Report()
|
||||
r.ok("all good")
|
||||
self.assertEqual(self._exit_code(r), 0)
|
||||
|
||||
def test_warning_only_exits_zero(self):
|
||||
r = chk.Report()
|
||||
r.warning("H", "f", "tok", "note")
|
||||
self.assertEqual(self._exit_code(r), 0)
|
||||
|
||||
|
||||
class RuleEReportTuple(unittest.TestCase):
|
||||
"""Assert Rule E records the full (rule, expected) tuple, not just token."""
|
||||
|
||||
def test_violation_tuple_fields(self):
|
||||
d = Path(tempfile.mkdtemp())
|
||||
try:
|
||||
(d / "docs").mkdir()
|
||||
(d / "docs" / "telemetry-runbook.md").write_text("`xrpl.tx.hash`")
|
||||
report = chk.Report()
|
||||
chk.run_rule_e_runbook(d, {"xrpl.network.id"}, report)
|
||||
self.assertEqual(len(report.violations), 1)
|
||||
rule, _loc, token, expected = report.violations[0]
|
||||
self.assertEqual(rule, "E")
|
||||
self.assertEqual(token, "xrpl.tx.hash")
|
||||
self.assertEqual(expected, "underscore, not dotted")
|
||||
finally:
|
||||
shutil.rmtree(d)
|
||||
|
||||
def test_clean_runbook_records_ok(self):
|
||||
d = Path(tempfile.mkdtemp())
|
||||
try:
|
||||
(d / "docs").mkdir()
|
||||
(d / "docs" / "telemetry-runbook.md").write_text(
|
||||
"`tx_hash` `consensus.round`"
|
||||
)
|
||||
report = chk.Report()
|
||||
chk.run_rule_e_runbook(d, {"tx_hash"}, report)
|
||||
self.assertEqual(report.violations, [])
|
||||
self.assertTrue(any("E:" in c for c in report.checked))
|
||||
finally:
|
||||
shutil.rmtree(d)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
3
.github/scripts/rename/README.md
vendored
3
.github/scripts/rename/README.md
vendored
@@ -34,6 +34,8 @@ run from the repository root.
|
||||
6. `.github/scripts/rename/config.sh`: This script will rename the config from
|
||||
`rippled.cfg` to `xrpld.cfg`, and updating the code accordingly. The old
|
||||
filename will still be accepted.
|
||||
7. `.github/scripts/rename/docs.sh`: This script will rename any lingering
|
||||
references of `ripple(d)` to `xrpl(d)` in code, comments, and documentation.
|
||||
|
||||
You can run all these scripts from the repository root as follows:
|
||||
|
||||
@@ -44,4 +46,5 @@ You can run all these scripts from the repository root as follows:
|
||||
./.github/scripts/rename/binary.sh .
|
||||
./.github/scripts/rename/namespace.sh .
|
||||
./.github/scripts/rename/config.sh .
|
||||
./.github/scripts/rename/docs.sh .
|
||||
```
|
||||
|
||||
13
.github/scripts/rename/binary.sh
vendored
13
.github/scripts/rename/binary.sh
vendored
@@ -6,11 +6,11 @@ set -e
|
||||
# On MacOS, ensure that GNU sed is installed and available as `gsed`.
|
||||
SED_COMMAND=sed
|
||||
if [[ "${OSTYPE}" == 'darwin'* ]]; then
|
||||
if ! command -v gsed &> /dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
SED_COMMAND=gsed
|
||||
if ! command -v gsed &>/dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
SED_COMMAND=gsed
|
||||
fi
|
||||
|
||||
# This script changes the binary name from `rippled` to `xrpld`, and reverses
|
||||
@@ -29,7 +29,7 @@ if [ ! -d "${DIRECTORY}" ]; then
|
||||
echo "Error: Directory '${DIRECTORY}' does not exist."
|
||||
exit 1
|
||||
fi
|
||||
pushd ${DIRECTORY}
|
||||
pushd "${DIRECTORY}"
|
||||
|
||||
# Remove the binary name override added by the cmake.sh script.
|
||||
${SED_COMMAND} -z -i -E 's@\s+# For the time being.+"rippled"\)@@' cmake/XrplCore.cmake
|
||||
@@ -49,6 +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
|
||||
|
||||
popd
|
||||
echo "Processing complete."
|
||||
|
||||
42
.github/scripts/rename/cmake.sh
vendored
42
.github/scripts/rename/cmake.sh
vendored
@@ -8,16 +8,16 @@ set -e
|
||||
SED_COMMAND=sed
|
||||
HEAD_COMMAND=head
|
||||
if [[ "${OSTYPE}" == 'darwin'* ]]; then
|
||||
if ! command -v gsed &> /dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
SED_COMMAND=gsed
|
||||
if ! command -v ghead &> /dev/null; then
|
||||
echo "Error: ghead is not installed. Please install it using 'brew install coreutils'."
|
||||
exit 1
|
||||
fi
|
||||
HEAD_COMMAND=ghead
|
||||
if ! command -v gsed &>/dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
SED_COMMAND=gsed
|
||||
if ! command -v ghead &>/dev/null; then
|
||||
echo "Error: ghead is not installed. Please install it using 'brew install coreutils'."
|
||||
exit 1
|
||||
fi
|
||||
HEAD_COMMAND=ghead
|
||||
fi
|
||||
|
||||
# This script renames CMake files from `RippleXXX.cmake` or `RippledXXX.cmake`
|
||||
@@ -38,16 +38,13 @@ if [ ! -d "${DIRECTORY}" ]; then
|
||||
echo "Error: Directory '${DIRECTORY}' does not exist."
|
||||
exit 1
|
||||
fi
|
||||
pushd ${DIRECTORY}
|
||||
pushd "${DIRECTORY}"
|
||||
|
||||
# Rename the files.
|
||||
find cmake -type f -name 'Rippled*.cmake' -exec bash -c 'mv "${1}" "${1/Rippled/Xrpl}"' - {} \;
|
||||
find cmake -type f -name 'Ripple*.cmake' -exec bash -c 'mv "${1}" "${1/Ripple/Xrpl}"' - {} \;
|
||||
if [ -e cmake/xrpl_add_test.cmake ]; then
|
||||
mv cmake/xrpl_add_test.cmake cmake/XrplAddTest.cmake
|
||||
fi
|
||||
if [ -e include/xrpl/proto/ripple.proto ]; then
|
||||
mv include/xrpl/proto/ripple.proto include/xrpl/proto/xrpl.proto
|
||||
mv include/xrpl/proto/ripple.proto include/xrpl/proto/xrpl.proto
|
||||
fi
|
||||
|
||||
# Rename inside the files.
|
||||
@@ -60,7 +57,6 @@ find cmake -type f -name '*.cmake' | while read -r FILE; do
|
||||
done
|
||||
${SED_COMMAND} -i -E 's/Rippled?/Xrpl/g' CMakeLists.txt
|
||||
${SED_COMMAND} -i 's/ripple/xrpl/g' CMakeLists.txt
|
||||
${SED_COMMAND} -i 's/include(xrpl_add_test)/include(XrplAddTest)/' src/tests/libxrpl/CMakeLists.txt
|
||||
${SED_COMMAND} -i 's/ripple.pb.h/xrpl.pb.h/' include/xrpl/protocol/messages.h
|
||||
${SED_COMMAND} -i 's/ripple.pb.h/xrpl.pb.h/' BUILD.md
|
||||
${SED_COMMAND} -i 's/ripple.pb.h/xrpl.pb.h/' BUILD.md
|
||||
@@ -71,14 +67,14 @@ ${SED_COMMAND} -i 's@xrpl/validator-keys-tool@ripple/validator-keys-tool@' cmake
|
||||
# Ensure the name of the binary and config remain 'rippled' for now.
|
||||
${SED_COMMAND} -i -E 's/xrpld(-example)?\.cfg/rippled\1.cfg/g' cmake/XrplInstall.cmake
|
||||
if grep -q '"xrpld"' cmake/XrplCore.cmake; then
|
||||
# The script has been rerun, so just restore the name of the binary.
|
||||
${SED_COMMAND} -i 's/"xrpld"/"rippled"/' cmake/XrplCore.cmake
|
||||
# The script has been rerun, so just restore the name of the binary.
|
||||
${SED_COMMAND} -i 's/"xrpld"/"rippled"/' cmake/XrplCore.cmake
|
||||
elif ! grep -q '"rippled"' cmake/XrplCore.cmake; then
|
||||
${HEAD_COMMAND} -n -1 cmake/XrplCore.cmake > cmake.tmp
|
||||
echo ' # For the time being, we will keep the name of the binary as it was.' >> cmake.tmp
|
||||
echo ' set_target_properties(xrpld PROPERTIES OUTPUT_NAME "rippled")' >> cmake.tmp
|
||||
tail -1 cmake/XrplCore.cmake >> cmake.tmp
|
||||
mv cmake.tmp cmake/XrplCore.cmake
|
||||
${HEAD_COMMAND} -n -1 cmake/XrplCore.cmake >cmake.tmp
|
||||
echo ' # For the time being, we will keep the name of the binary as it was.' >>cmake.tmp
|
||||
echo ' set_target_properties(xrpld PROPERTIES OUTPUT_NAME "rippled")' >>cmake.tmp
|
||||
tail -1 cmake/XrplCore.cmake >>cmake.tmp
|
||||
mv cmake.tmp cmake/XrplCore.cmake
|
||||
fi
|
||||
|
||||
# Restore the symlink from 'xrpld' to 'rippled'.
|
||||
|
||||
33
.github/scripts/rename/config.sh
vendored
33
.github/scripts/rename/config.sh
vendored
@@ -6,11 +6,11 @@ set -e
|
||||
# On MacOS, ensure that GNU sed is installed and available as `gsed`.
|
||||
SED_COMMAND=sed
|
||||
if [[ "${OSTYPE}" == 'darwin'* ]]; then
|
||||
if ! command -v gsed &> /dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
SED_COMMAND=gsed
|
||||
if ! command -v gsed &>/dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
SED_COMMAND=gsed
|
||||
fi
|
||||
|
||||
# This script renames the config from `rippled.cfg` to `xrpld.cfg`, and updates
|
||||
@@ -28,42 +28,41 @@ if [ ! -d "${DIRECTORY}" ]; then
|
||||
echo "Error: Directory '${DIRECTORY}' does not exist."
|
||||
exit 1
|
||||
fi
|
||||
pushd ${DIRECTORY}
|
||||
pushd "${DIRECTORY}"
|
||||
|
||||
# Add the xrpld.cfg to the .gitignore.
|
||||
if ! grep -q 'xrpld.cfg' .gitignore; then
|
||||
${SED_COMMAND} -i '/rippled.cfg/a\
|
||||
${SED_COMMAND} -i '/rippled.cfg/a\
|
||||
/xrpld.cfg' .gitignore
|
||||
fi
|
||||
|
||||
# Rename the files.
|
||||
if [ -e rippled.cfg ]; then
|
||||
mv rippled.cfg xrpld.cfg
|
||||
mv rippled.cfg xrpld.cfg
|
||||
fi
|
||||
if [ -e cfg/rippled-example.cfg ]; then
|
||||
mv cfg/rippled-example.cfg cfg/xrpld-example.cfg
|
||||
mv cfg/rippled-example.cfg cfg/xrpld-example.cfg
|
||||
fi
|
||||
|
||||
# Rename inside the files.
|
||||
DIRECTORIES=("cfg" "cmake" "include" "src")
|
||||
for DIRECTORY in "${DIRECTORIES[@]}"; do
|
||||
echo "Processing directory: ${DIRECTORY}"
|
||||
echo "Processing directory: ${DIRECTORY}"
|
||||
|
||||
find "${DIRECTORY}" -type f \( -name "*.h" -o -name "*.hpp" -o -name "*.ipp" -o -name "*.cpp" -o -name "*.cmake" -o -name "*.txt" -o -name "*.cfg" -o -name "*.md" \) | while read -r FILE; do
|
||||
echo "Processing file: ${FILE}"
|
||||
${SED_COMMAND} -i -E 's/rippled(-example)?[ .]cfg/xrpld\1.cfg/g' "${FILE}"
|
||||
done
|
||||
find "${DIRECTORY}" -type f \( -name "*.h" -o -name "*.hpp" -o -name "*.ipp" -o -name "*.cpp" -o -name "*.cmake" -o -name "*.txt" -o -name "*.cfg" -o -name "*.md" \) | while read -r FILE; do
|
||||
echo "Processing file: ${FILE}"
|
||||
${SED_COMMAND} -i -E 's/rippled(-example)?[ .]cfg/xrpld\1.cfg/g' "${FILE}"
|
||||
${SED_COMMAND} -i 's/rippleConfig/xrpldConfig/g' "${FILE}"
|
||||
done
|
||||
done
|
||||
${SED_COMMAND} -i 's/rippled/xrpld/g' cfg/xrpld-example.cfg
|
||||
${SED_COMMAND} -i 's/rippled/xrpld/g' src/test/core/Config_test.cpp
|
||||
${SED_COMMAND} -i 's/ripplevalidators/xrplvalidators/g' src/test/core/Config_test.cpp # cspell: disable-line
|
||||
${SED_COMMAND} -i 's/rippleConfig/xrpldConfig/g' src/test/core/Config_test.cpp
|
||||
${SED_COMMAND} -i 's@ripple/@xrpld/@g' src/test/core/Config_test.cpp
|
||||
${SED_COMMAND} -i 's/Rippled/File/g' src/test/core/Config_test.cpp
|
||||
|
||||
|
||||
# Restore the old config file name in the code that maintains support for now.
|
||||
${SED_COMMAND} -i 's/configLegacyName = "xrpld.cfg"/configLegacyName = "rippled.cfg"/g' src/xrpld/core/detail/Config.cpp
|
||||
${SED_COMMAND} -i 's/kConfigLegacyName = "xrpld.cfg"/kConfigLegacyName = "rippled.cfg"/g' src/xrpld/core/detail/Config.cpp
|
||||
|
||||
# Restore an URL.
|
||||
${SED_COMMAND} -i 's/connect-your-xrpld-to-the-xrp-test-net.html/connect-your-rippled-to-the-xrp-test-net.html/g' cfg/xrpld-example.cfg
|
||||
|
||||
60
.github/scripts/rename/copyright.sh
vendored
60
.github/scripts/rename/copyright.sh
vendored
@@ -6,11 +6,11 @@ set -e
|
||||
# On MacOS, ensure that GNU sed is installed and available as `gsed`.
|
||||
SED_COMMAND=sed
|
||||
if [[ "${OSTYPE}" == 'darwin'* ]]; then
|
||||
if ! command -v gsed &> /dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
SED_COMMAND=gsed
|
||||
if ! command -v gsed &>/dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
SED_COMMAND=gsed
|
||||
fi
|
||||
|
||||
# This script removes superfluous copyright notices in source and header files
|
||||
@@ -31,7 +31,7 @@ if [ ! -d "${DIRECTORY}" ]; then
|
||||
echo "Error: Directory '${DIRECTORY}' does not exist."
|
||||
exit 1
|
||||
fi
|
||||
pushd ${DIRECTORY}
|
||||
pushd "${DIRECTORY}"
|
||||
|
||||
# Prevent sed and echo from removing newlines and tabs in string literals by
|
||||
# temporarily replacing them with placeholders. This only affects one file.
|
||||
@@ -43,56 +43,56 @@ ${SED_COMMAND} -i -E "s@\\\t@${PLACEHOLDER_TAB}@g" src/test/rpc/ValidatorInfo_te
|
||||
# Process the include/ and src/ directories.
|
||||
DIRECTORIES=("include" "src")
|
||||
for DIRECTORY in "${DIRECTORIES[@]}"; do
|
||||
echo "Processing directory: ${DIRECTORY}"
|
||||
echo "Processing directory: ${DIRECTORY}"
|
||||
|
||||
find "${DIRECTORY}" -type f \( -name "*.h" -o -name "*.hpp" -o -name "*.ipp" -o -name "*.cpp" -o -name "*.macro" \) | while read -r FILE; do
|
||||
echo "Processing file: ${FILE}"
|
||||
# Handle the cases where the copyright notice is enclosed in /* ... */
|
||||
# and usually surrounded by //---- and //======.
|
||||
${SED_COMMAND} -z -i -E 's@^//-------+\n+@@' "${FILE}"
|
||||
${SED_COMMAND} -z -i -E 's@^.*Copyright.+(Ripple|Bougalis|Falco|Hinnant|Null|Ritchford|XRPLF).+PERFORMANCE OF THIS SOFTWARE\.\n\*/\n+@@' "${FILE}" # cspell: ignore Bougalis Falco Hinnant Ritchford
|
||||
${SED_COMMAND} -z -i -E 's@^//=======+\n+@@' "${FILE}"
|
||||
find "${DIRECTORY}" -type f \( -name "*.h" -o -name "*.hpp" -o -name "*.ipp" -o -name "*.cpp" -o -name "*.macro" \) | while read -r FILE; do
|
||||
echo "Processing file: ${FILE}"
|
||||
# Handle the cases where the copyright notice is enclosed in /* ... */
|
||||
# and usually surrounded by //---- and //======.
|
||||
${SED_COMMAND} -z -i -E 's@^//-------+\n+@@' "${FILE}"
|
||||
${SED_COMMAND} -z -i -E 's@^.*Copyright.+(Ripple|Bougalis|Falco|Hinnant|Null|Ritchford|XRPLF).+PERFORMANCE OF THIS SOFTWARE\.\n\*/\n+@@' "${FILE}" # cspell: ignore Bougalis Falco Hinnant Ritchford
|
||||
${SED_COMMAND} -z -i -E 's@^//=======+\n+@@' "${FILE}"
|
||||
|
||||
# Handle the cases where the copyright notice is commented out with //.
|
||||
${SED_COMMAND} -z -i -E 's@^//\n// Copyright.+Falco \(vinnie dot falco at gmail dot com\)\n//\n+@@' "${FILE}" # cspell: ignore Vinnie Falco
|
||||
done
|
||||
# Handle the cases where the copyright notice is commented out with //.
|
||||
${SED_COMMAND} -z -i -E 's@^//\n// Copyright.+Falco \(vinnie dot falco at gmail dot com\)\n//\n+@@' "${FILE}" # cspell: ignore Vinnie Falco
|
||||
done
|
||||
done
|
||||
|
||||
# Restore copyright notices that were removed from specific files, without
|
||||
# restoring the verbiage that is already present in LICENSE.md. Ensure that if
|
||||
# the script is run multiple times, duplicate notices are not added.
|
||||
if ! grep -q 'Raw Material Software' include/xrpl/beast/core/CurrentThreadName.h; then
|
||||
echo -e "// Portions of this file are from JUCE (http://www.juce.com).\n// Copyright (c) 2013 - Raw Material Software Ltd.\n// Please visit http://www.juce.com\n\n$(cat include/xrpl/beast/core/CurrentThreadName.h)" > include/xrpl/beast/core/CurrentThreadName.h
|
||||
echo -e "// Portions of this file are from JUCE (http://www.juce.com).\n// Copyright (c) 2013 - Raw Material Software Ltd.\n// Please visit http://www.juce.com\n\n$(cat include/xrpl/beast/core/CurrentThreadName.h)" >include/xrpl/beast/core/CurrentThreadName.h
|
||||
fi
|
||||
if ! grep -q 'Dev Null' src/test/app/NetworkID_test.cpp; then
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/app/NetworkID_test.cpp)" > src/test/app/NetworkID_test.cpp
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/app/NetworkID_test.cpp)" >src/test/app/NetworkID_test.cpp
|
||||
fi
|
||||
if ! grep -q 'Dev Null' src/test/app/tx/apply_test.cpp; then
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/app/tx/apply_test.cpp)" > src/test/app/tx/apply_test.cpp
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/app/tx/apply_test.cpp)" >src/test/app/tx/apply_test.cpp
|
||||
fi
|
||||
if ! grep -q 'Dev Null' src/test/rpc/ManifestRPC_test.cpp; then
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/rpc/ManifestRPC_test.cpp)" > src/test/rpc/ManifestRPC_test.cpp
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/rpc/ManifestRPC_test.cpp)" >src/test/rpc/ManifestRPC_test.cpp
|
||||
fi
|
||||
if ! grep -q 'Dev Null' src/test/rpc/ValidatorInfo_test.cpp; then
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/rpc/ValidatorInfo_test.cpp)" > src/test/rpc/ValidatorInfo_test.cpp
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/rpc/ValidatorInfo_test.cpp)" >src/test/rpc/ValidatorInfo_test.cpp
|
||||
fi
|
||||
if ! grep -q 'Dev Null' src/xrpld/rpc/handlers/DoManifest.cpp; then
|
||||
echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/DoManifest.cpp)" > src/xrpld/rpc/handlers/DoManifest.cpp
|
||||
if ! grep -q 'Dev Null' src/xrpld/rpc/handlers/server_info/Manifest.cpp; then
|
||||
echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/server_info/Manifest.cpp)" >src/xrpld/rpc/handlers/server_info/Manifest.cpp
|
||||
fi
|
||||
if ! grep -q 'Dev Null' src/xrpld/rpc/handlers/ValidatorInfo.cpp; then
|
||||
echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/ValidatorInfo.cpp)" > src/xrpld/rpc/handlers/ValidatorInfo.cpp
|
||||
if ! grep -q 'Dev Null' src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp; then
|
||||
echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp)" >src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp
|
||||
fi
|
||||
if ! grep -q 'Bougalis' include/xrpl/basics/SlabAllocator.h; then
|
||||
echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/SlabAllocator.h)" > include/xrpl/basics/SlabAllocator.h # cspell: ignore Nikolaos Bougalis nikb
|
||||
echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/SlabAllocator.h)" >include/xrpl/basics/SlabAllocator.h # cspell: ignore Nikolaos Bougalis nikb
|
||||
fi
|
||||
if ! grep -q 'Bougalis' include/xrpl/basics/spinlock.h; then
|
||||
echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/spinlock.h)" > include/xrpl/basics/spinlock.h # cspell: ignore Nikolaos Bougalis nikb
|
||||
echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/spinlock.h)" >include/xrpl/basics/spinlock.h # cspell: ignore Nikolaos Bougalis nikb
|
||||
fi
|
||||
if ! grep -q 'Bougalis' include/xrpl/basics/tagged_integer.h; then
|
||||
echo -e "// Copyright (c) 2014, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/tagged_integer.h)" > include/xrpl/basics/tagged_integer.h # cspell: ignore Nikolaos Bougalis nikb
|
||||
echo -e "// Copyright (c) 2014, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/tagged_integer.h)" >include/xrpl/basics/tagged_integer.h # cspell: ignore Nikolaos Bougalis nikb
|
||||
fi
|
||||
if ! grep -q 'Ritchford' include/xrpl/beast/utility/Zero.h; then
|
||||
echo -e "// Copyright (c) 2014, Tom Ritchford <tom@swirly.com>\n\n$(cat include/xrpl/beast/utility/Zero.h)" > include/xrpl/beast/utility/Zero.h # cspell: ignore Ritchford
|
||||
echo -e "// Copyright (c) 2014, Tom Ritchford <tom@swirly.com>\n\n$(cat include/xrpl/beast/utility/Zero.h)" >include/xrpl/beast/utility/Zero.h # cspell: ignore Ritchford
|
||||
fi
|
||||
|
||||
# Restore newlines and tabs in string literals in the affected file.
|
||||
|
||||
10
.github/scripts/rename/definitions.sh
vendored
10
.github/scripts/rename/definitions.sh
vendored
@@ -6,11 +6,11 @@ set -e
|
||||
# On MacOS, ensure that GNU sed is installed and available as `gsed`.
|
||||
SED_COMMAND=sed
|
||||
if [[ "${OSTYPE}" == 'darwin'* ]]; then
|
||||
if ! command -v gsed &> /dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
SED_COMMAND=gsed
|
||||
if ! command -v gsed &>/dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
SED_COMMAND=gsed
|
||||
fi
|
||||
|
||||
# This script renames definitions, such as include guards, in this project.
|
||||
|
||||
96
.github/scripts/rename/docs.sh
vendored
Executable file
96
.github/scripts/rename/docs.sh
vendored
Executable file
@@ -0,0 +1,96 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Exit the script as soon as an error occurs.
|
||||
set -e
|
||||
|
||||
# On MacOS, ensure that GNU sed is installed and available as `gsed`.
|
||||
SED_COMMAND=sed
|
||||
if [[ "${OSTYPE}" == 'darwin'* ]]; then
|
||||
if ! command -v gsed &>/dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
SED_COMMAND=gsed
|
||||
fi
|
||||
|
||||
# This script renames all remaining references to `ripple` and `rippled` to
|
||||
# `xrpl` and `xrpld`, respectively, in code, comments, and documentation.
|
||||
# Usage: .github/scripts/rename/docs.sh <repository directory>
|
||||
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "Usage: $0 <repository directory>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DIRECTORY=$1
|
||||
echo "Processing directory: ${DIRECTORY}"
|
||||
if [ ! -d "${DIRECTORY}" ]; then
|
||||
echo "Error: Directory '${DIRECTORY}' does not exist."
|
||||
exit 1
|
||||
fi
|
||||
pushd "${DIRECTORY}"
|
||||
|
||||
find . -type f \( -name "*.h" -o -name "*.hpp" -o -name "*.ipp" -o -name "*.cpp" -o -name "*.txt" -o -name "*.cfg" -o -name "*.md" -o -name "*.proto" \) -not -path "./.github/scripts/*" | while read -r FILE; do
|
||||
echo "Processing file: ${FILE}"
|
||||
${SED_COMMAND} -i 's/rippleLockEscrowMPT/lockEscrowMPT/g' "${FILE}"
|
||||
${SED_COMMAND} -i 's/rippleUnlockEscrowMPT/unlockEscrowMPT/g' "${FILE}"
|
||||
${SED_COMMAND} -i 's/rippleCredit/directSendNoFee/g' "${FILE}"
|
||||
${SED_COMMAND} -i 's/rippleSend/directSendNoLimit/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's@([^/+-])rippled@\1xrpld@g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's@([^/+-])Rippled@\1Xrpld@g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/^rippled/xrpld/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/^Rippled/Xrpld/g' "${FILE}"
|
||||
# cspell: disable
|
||||
${SED_COMMAND} -i -E 's/(r|R)ipple (a|A)ddress/XRPL address/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/(r|R)ipple (a|A)ccount/XRPL account/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/(r|R)ipple (a|A)lgorithm/XRPL algorithm/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/(r|R)ipple (c|C)lient/XRPL client/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/(r|R)ipple (c|C)luster/XRPL cluster/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/(r|R)ipple (c|C)onsensus/XRPL consensus/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/(r|R)ipple (d|D)efault/XRPL default/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/(r|R)ipple (e|E)poch/XRPL epoch/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/(r|R)ipple (f|F)eature/XRPL feature/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/(r|R)ipple (n|N)etwork/XRPL network/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/(r|R)ipple (p|P)ayment/XRPL payment/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/(r|R)ipple (p|P)rotocol/XRPL protocol/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/(r|R)ipple (r|R)epository/XRPL repository/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/(r|R)ipple RPC/XRPL RPC/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/(r|R)ipple (s|S)erialization/XRPL serialization/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/(r|R)ipple (s|S)erver/XRPL server/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/(r|R)ipple (s|S)pecific/XRPL specific/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/(r|R)ipple Source/XRPL Source/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/(r|R)ipple (t|T)imestamp/XRPL timestamp/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/(r|R)ipple uses the consensus/XRPL uses the consensus/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/(r|R)ipple (v|V)alidator/XRPL validator/g' "${FILE}"
|
||||
# cspell: enable
|
||||
${SED_COMMAND} -i 's/RippleLib/XrplLib/g' "${FILE}"
|
||||
${SED_COMMAND} -i 's/ripple-lib/XrplLib/g' "${FILE}"
|
||||
${SED_COMMAND} -i 's@opt/ripple/@opt/xrpld/@g' "${FILE}"
|
||||
${SED_COMMAND} -i 's@src/ripple/@src/xrpld/@g' "${FILE}"
|
||||
${SED_COMMAND} -i 's@ripple/app/@xrpld/app/@g' "${FILE}"
|
||||
${SED_COMMAND} -i 's@github.com/ripple/rippled@github.com/XRPLF/rippled@g' "${FILE}"
|
||||
${SED_COMMAND} -i 's/\ba xrpl/an xrpl/g' "${FILE}"
|
||||
${SED_COMMAND} -i 's/\ba XRPL/an XRPL/g' "${FILE}"
|
||||
done
|
||||
${SED_COMMAND} -i 's/ripple_libs/xrpl_libs/' BUILD.md
|
||||
${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/Doxyfile
|
||||
${SED_COMMAND} -i 's/ripple_basics/basics/' include/xrpl/basics/CountedObject.h
|
||||
${SED_COMMAND} -i 's/<ripple/<xrpl/' include/xrpl/protocol/AccountID.h
|
||||
${SED_COMMAND} -i 's/Ripple:/the XRPL:/g' include/xrpl/protocol/SecretKey.h
|
||||
${SED_COMMAND} -i 's/Ripple:/the XRPL:/g' include/xrpl/protocol/Seed.h
|
||||
${SED_COMMAND} -i 's/ripple/xrpl/g' src/test/README.md
|
||||
${SED_COMMAND} -i 's/www.ripple.com/www.xrpl.org/g' src/test/protocol/Seed_test.cpp
|
||||
|
||||
# Restore specific changes.
|
||||
${SED_COMMAND} -i 's@b5efcc/src/xrpld@b5efcc/src/ripple@' include/xrpl/protocol/README.md
|
||||
${SED_COMMAND} -i 's/dbPrefix_ = "xrpldb"/dbPrefix_ = "rippledb"/' src/xrpld/app/misc/SHAMapStoreImp.h # cspell: disable-line
|
||||
${SED_COMMAND} -i 's/kConfigLegacyName = "xrpld.cfg"/kConfigLegacyName = "rippled.cfg"/' src/xrpld/core/detail/Config.cpp
|
||||
|
||||
popd
|
||||
echo "Renaming complete."
|
||||
4
.github/scripts/rename/include.sh
vendored
4
.github/scripts/rename/include.sh
vendored
@@ -23,8 +23,8 @@ fi
|
||||
find "${DIRECTORY}" -type f \( -name "*.h" -o -name "*.hpp" -o -name "*.ipp" \) | while read -r FILE; do
|
||||
echo "Processing file: ${FILE}"
|
||||
if grep -q "#ifndef XRPL_" "${FILE}"; then
|
||||
echo "Please replace all include guards by #pragma once."
|
||||
exit 1
|
||||
echo "Please replace all include guards by #pragma once."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo "Checking complete."
|
||||
|
||||
27
.github/scripts/rename/namespace.sh
vendored
27
.github/scripts/rename/namespace.sh
vendored
@@ -6,11 +6,11 @@ set -e
|
||||
# On MacOS, ensure that GNU sed is installed and available as `gsed`.
|
||||
SED_COMMAND=sed
|
||||
if [[ "${OSTYPE}" == 'darwin'* ]]; then
|
||||
if ! command -v gsed &> /dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
SED_COMMAND=gsed
|
||||
if ! command -v gsed &>/dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
SED_COMMAND=gsed
|
||||
fi
|
||||
|
||||
# This script renames the `ripple` namespace to `xrpl` in this project.
|
||||
@@ -31,18 +31,19 @@ if [ ! -d "${DIRECTORY}" ]; then
|
||||
echo "Error: Directory '${DIRECTORY}' does not exist."
|
||||
exit 1
|
||||
fi
|
||||
pushd ${DIRECTORY}
|
||||
pushd "${DIRECTORY}"
|
||||
|
||||
DIRECTORIES=("include" "src" "tests")
|
||||
for DIRECTORY in "${DIRECTORIES[@]}"; do
|
||||
echo "Processing directory: ${DIRECTORY}"
|
||||
echo "Processing directory: ${DIRECTORY}"
|
||||
|
||||
find "${DIRECTORY}" -type f \( -name "*.h" -o -name "*.hpp" -o -name "*.ipp" -o -name "*.cpp" \) | while read -r FILE; do
|
||||
echo "Processing file: ${FILE}"
|
||||
${SED_COMMAND} -i 's/namespace ripple/namespace xrpl/g' "${FILE}"
|
||||
${SED_COMMAND} -i 's/ripple::/xrpl::/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/(BEAST_DEFINE_TESTSUITE.+)ripple(.+)/\1xrpl\2/g' "${FILE}"
|
||||
done
|
||||
find "${DIRECTORY}" -type f \( -name "*.h" -o -name "*.hpp" -o -name "*.ipp" -o -name "*.cpp" -o -name "*.macro" \) | while read -r FILE; do
|
||||
echo "Processing file: ${FILE}"
|
||||
${SED_COMMAND} -i 's/namespace ripple/namespace xrpl/g' "${FILE}"
|
||||
${SED_COMMAND} -i 's/ripple::/xrpl::/g' "${FILE}"
|
||||
${SED_COMMAND} -i 's/"ripple:/"xrpl::/g' "${FILE}"
|
||||
${SED_COMMAND} -i -E 's/(BEAST_DEFINE_TESTSUITE.+)ripple(.+)/\1xrpl\2/g' "${FILE}"
|
||||
done
|
||||
done
|
||||
|
||||
# Special case for NuDBFactory that has ripple twice in the test suite name.
|
||||
|
||||
589
.github/scripts/strategy-matrix/generate.py
vendored
589
.github/scripts/strategy-matrix/generate.py
vendored
@@ -1,333 +1,322 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import dataclasses
|
||||
import itertools
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
THIS_DIR = Path(__file__).parent.resolve()
|
||||
|
||||
_BASE_CMAKE_ARGS = ["-Dtests=ON", "-Dwerr=ON", "-Dxrpld=ON", "-Dwextra=ON"]
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
architecture: list[dict]
|
||||
os: list[dict]
|
||||
# Maps sanitizer names (as used in cmake) to short config-name suffixes.
|
||||
_SANITIZER_SUFFIX: dict[str, str] = {
|
||||
"address": "asan",
|
||||
"undefinedbehavior": "ubsan",
|
||||
"thread": "tsan",
|
||||
}
|
||||
|
||||
|
||||
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()
|
||||
if extra_args:
|
||||
args.extend(extra_args.split())
|
||||
return " ".join(args)
|
||||
|
||||
|
||||
def runs_on_event(exclude_event_types: list[str], event: str | None) -> bool:
|
||||
"""Whether a config should run for the current event.
|
||||
|
||||
'exclude_event_types' is a list of GitHub event names (e.g.
|
||||
["pull_request"]) on which the config should NOT run; an empty list means
|
||||
the config runs on every event. When no event is given (event is None), no
|
||||
filtering is applied.
|
||||
"""
|
||||
if event is None:
|
||||
return True
|
||||
return event not in exclude_event_types
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Input types — shapes of the JSON config files
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class LinuxConfig:
|
||||
"""One entry in linux.json's 'configs' or 'package_configs' arrays."""
|
||||
|
||||
compiler: list[str]
|
||||
build_type: list[str]
|
||||
cmake_args: list[str]
|
||||
arch: list[str]
|
||||
sanitizers: list[str] = dataclasses.field(default_factory=list)
|
||||
suffix: str = ""
|
||||
extra_cmake_args: str = ""
|
||||
image: str = "" # only used by package_configs entries
|
||||
# List of GitHub event names (e.g. "pull_request") on which this config
|
||||
# should NOT run. Empty means it runs on every event.
|
||||
exclude_event_types: list[str] = dataclasses.field(default_factory=list)
|
||||
|
||||
|
||||
"""
|
||||
Generate a strategy matrix for GitHub Actions CI.
|
||||
@dataclasses.dataclass
|
||||
class LinuxFile:
|
||||
"""Shape of linux.json."""
|
||||
|
||||
On each PR commit we will build a selection of Debian, RHEL, Ubuntu, MacOS, and
|
||||
Windows configurations, while upon merge into the develop or release branches,
|
||||
we will build all configurations, and test most of them.
|
||||
image_tag: str
|
||||
configs: dict[str, list[LinuxConfig]] # distro → configs
|
||||
package_configs: dict[str, list[LinuxConfig]] # distro → packaging configs
|
||||
|
||||
We will further set additional CMake arguments as follows:
|
||||
- All builds will have the `tests`, `werr`, and `xrpld` options.
|
||||
- All builds will have the `wextra` option except for GCC 12 and Clang 16.
|
||||
- All release builds will have the `assert` option.
|
||||
- Certain Debian Bookworm configurations will change the reference fee, enable
|
||||
codecov, and enable voidstar in PRs.
|
||||
"""
|
||||
@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()
|
||||
}
|
||||
|
||||
def generate_strategy_matrix(all: bool, config: Config) -> list:
|
||||
configurations = []
|
||||
for architecture, os, build_type, cmake_args in itertools.product(
|
||||
config.architecture, config.os, config.build_type, config.cmake_args
|
||||
):
|
||||
# The default CMake target is 'all' for Linux and MacOS and 'install'
|
||||
# for Windows, but it can get overridden for certain configurations.
|
||||
cmake_target = "install" if os["distro_name"] == "windows" else "all"
|
||||
|
||||
# We build and test all configurations by default, except for Windows in
|
||||
# Debug, because it is too slow, as well as when code coverage is
|
||||
# enabled as that mode already runs the tests.
|
||||
build_only = False
|
||||
if os["distro_name"] == "windows" and build_type == "Debug":
|
||||
build_only = True
|
||||
|
||||
# Only generate a subset of configurations in PRs.
|
||||
if not all:
|
||||
# Debian:
|
||||
# - Bookworm using GCC 13: Release on linux/amd64, set the reference
|
||||
# fee to 500.
|
||||
# - Bookworm using GCC 15: Debug on linux/amd64, enable code
|
||||
# coverage (which will be done below).
|
||||
# - Bookworm using Clang 16: Debug on linux/amd64, enable voidstar.
|
||||
# - Bookworm using Clang 17: Release on linux/amd64, set the
|
||||
# reference fee to 1000.
|
||||
# - Bookworm using Clang 20: Debug on linux/amd64.
|
||||
if os["distro_name"] == "debian":
|
||||
skip = True
|
||||
if os["distro_version"] == "bookworm":
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-13"
|
||||
and build_type == "Release"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
cmake_args = f"-DUNIT_TEST_REFERENCE_FEE=500 {cmake_args}"
|
||||
skip = False
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-15"
|
||||
and build_type == "Debug"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
skip = False
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-16"
|
||||
and build_type == "Debug"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
cmake_args = f"-Dvoidstar=ON {cmake_args}"
|
||||
skip = False
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-17"
|
||||
and build_type == "Release"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
cmake_args = f"-DUNIT_TEST_REFERENCE_FEE=1000 {cmake_args}"
|
||||
skip = False
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-20"
|
||||
and build_type == "Debug"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
skip = False
|
||||
if skip:
|
||||
continue
|
||||
|
||||
# RHEL:
|
||||
# - 9 using GCC 12: Debug on linux/amd64.
|
||||
# - 10 using Clang: Release on linux/amd64.
|
||||
if os["distro_name"] == "rhel":
|
||||
skip = True
|
||||
if os["distro_version"] == "9":
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
|
||||
and build_type == "Debug"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
skip = False
|
||||
elif os["distro_version"] == "10":
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-any"
|
||||
and build_type == "Release"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
skip = False
|
||||
if skip:
|
||||
continue
|
||||
|
||||
# Ubuntu:
|
||||
# - Jammy using GCC 12: Debug on linux/arm64.
|
||||
# - Noble using GCC 14: Release on linux/amd64.
|
||||
# - Noble using Clang 18: Debug on linux/amd64.
|
||||
# - Noble using Clang 19: Release on linux/arm64.
|
||||
if os["distro_name"] == "ubuntu":
|
||||
skip = True
|
||||
if os["distro_version"] == "jammy":
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
|
||||
and build_type == "Debug"
|
||||
and architecture["platform"] == "linux/arm64"
|
||||
):
|
||||
skip = False
|
||||
elif os["distro_version"] == "noble":
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-14"
|
||||
and build_type == "Release"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
skip = False
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-18"
|
||||
and build_type == "Debug"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
skip = False
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-19"
|
||||
and build_type == "Release"
|
||||
and architecture["platform"] == "linux/arm64"
|
||||
):
|
||||
skip = False
|
||||
if skip:
|
||||
continue
|
||||
|
||||
# MacOS:
|
||||
# - Debug on macos/arm64.
|
||||
if os["distro_name"] == "macos" and not (
|
||||
build_type == "Debug" and architecture["platform"] == "macos/arm64"
|
||||
):
|
||||
continue
|
||||
|
||||
# Windows:
|
||||
# - Release on windows/amd64.
|
||||
if os["distro_name"] == "windows" and not (
|
||||
build_type == "Release" and architecture["platform"] == "windows/amd64"
|
||||
):
|
||||
continue
|
||||
|
||||
# Additional CMake arguments.
|
||||
cmake_args = f"{cmake_args} -Dtests=ON -Dwerr=ON -Dxrpld=ON"
|
||||
if not f"{os['compiler_name']}-{os['compiler_version']}" in [
|
||||
"gcc-12",
|
||||
"clang-16",
|
||||
]:
|
||||
cmake_args = f"{cmake_args} -Dwextra=ON"
|
||||
if build_type == "Release":
|
||||
cmake_args = f"{cmake_args} -Dassert=ON"
|
||||
|
||||
# We skip all RHEL on arm64 due to a build failure that needs further
|
||||
# investigation.
|
||||
if os["distro_name"] == "rhel" and architecture["platform"] == "linux/arm64":
|
||||
continue
|
||||
|
||||
# We skip all clang 20+ on arm64 due to Boost build error.
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}"
|
||||
in ["clang-20", "clang-21"]
|
||||
and architecture["platform"] == "linux/arm64"
|
||||
):
|
||||
continue
|
||||
|
||||
# Enable code coverage for Debian Bookworm using GCC 15 in Debug on
|
||||
# linux/amd64
|
||||
if (
|
||||
f"{os['distro_name']}-{os['distro_version']}" == "debian-bookworm"
|
||||
and f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-15"
|
||||
and build_type == "Debug"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
cmake_args = f"{cmake_args} -Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 -DCMAKE_CXX_FLAGS=-O0"
|
||||
|
||||
# Enable unity build for Ubuntu Jammy using GCC 12 in Debug on
|
||||
# linux/amd64.
|
||||
if (
|
||||
f"{os['distro_name']}-{os['distro_version']}" == "ubuntu-jammy"
|
||||
and f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
|
||||
and build_type == "Debug"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
cmake_args = f"{cmake_args} -Dunity=ON"
|
||||
|
||||
# Generate a unique name for the configuration, e.g. macos-arm64-debug
|
||||
# or debian-bookworm-gcc-12-amd64-release.
|
||||
config_name = os["distro_name"]
|
||||
if (n := os["distro_version"]) != "":
|
||||
config_name += f"-{n}"
|
||||
if (n := os["compiler_name"]) != "":
|
||||
config_name += f"-{n}"
|
||||
if (n := os["compiler_version"]) != "":
|
||||
config_name += f"-{n}"
|
||||
config_name += (
|
||||
f"-{architecture['platform'][architecture['platform'].find('/')+1:]}"
|
||||
return cls(
|
||||
image_tag=data["image_tag"],
|
||||
configs=parse(data["configs"]),
|
||||
package_configs=parse(data.get("package_configs", {})),
|
||||
)
|
||||
config_name += f"-{build_type.lower()}"
|
||||
if "-Dcoverage=ON" in cmake_args:
|
||||
config_name += "-coverage"
|
||||
if "-Dunity=ON" in cmake_args:
|
||||
config_name += "-unity"
|
||||
|
||||
# Add the configuration to the list, with the most unique fields first,
|
||||
# so that they are easier to identify in the GitHub Actions UI, as long
|
||||
# names get truncated.
|
||||
# Add Address and Thread (both coupled with UB) sanitizers for specific bookworm distros.
|
||||
# GCC-Asan rippled-embedded tests are failing because of https://github.com/google/sanitizers/issues/856
|
||||
if (
|
||||
os["distro_version"] == "bookworm"
|
||||
and f"{os['compiler_name']}-{os['compiler_version']}" == "clang-20"
|
||||
):
|
||||
# Add ASAN + UBSAN configuration.
|
||||
configurations.append(
|
||||
{
|
||||
"config_name": config_name + "-asan-ubsan",
|
||||
"cmake_args": cmake_args,
|
||||
"cmake_target": cmake_target,
|
||||
"build_only": build_only,
|
||||
"build_type": build_type,
|
||||
"os": os,
|
||||
"architecture": architecture,
|
||||
"sanitizers": "address,undefinedbehavior",
|
||||
}
|
||||
)
|
||||
# TSAN is deactivated due to seg faults with latest compilers.
|
||||
activate_tsan = False
|
||||
if activate_tsan:
|
||||
configurations.append(
|
||||
{
|
||||
"config_name": config_name + "-tsan-ubsan",
|
||||
"cmake_args": cmake_args,
|
||||
"cmake_target": cmake_target,
|
||||
"build_only": build_only,
|
||||
"build_type": build_type,
|
||||
"os": os,
|
||||
"architecture": architecture,
|
||||
"sanitizers": "thread,undefinedbehavior",
|
||||
}
|
||||
|
||||
@dataclasses.dataclass
|
||||
class PlatformConfig:
|
||||
"""One entry in macos.json's or windows.json's 'configs' array."""
|
||||
|
||||
build_type: list[str]
|
||||
build_only: bool = False # if true, skip tests (e.g. macos/Windows Debug)
|
||||
extra_cmake_args: str = ""
|
||||
# List of GitHub event names (e.g. "pull_request") on which this config
|
||||
# should NOT run. Empty means it runs on every event.
|
||||
exclude_event_types: list[str] = dataclasses.field(default_factory=list)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if isinstance(self.build_type, str):
|
||||
self.build_type = [self.build_type]
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class PlatformFile:
|
||||
"""Shape of macos.json and windows.json."""
|
||||
|
||||
platform: str # e.g. "macos/arm64" or "windows/amd64"
|
||||
runner: list[str] # GitHub Actions runner labels
|
||||
configs: list[PlatformConfig]
|
||||
|
||||
@classmethod
|
||||
def load(cls, path: Path) -> "PlatformFile":
|
||||
data = json.loads(path.read_text())
|
||||
return cls(
|
||||
platform=data["platform"],
|
||||
runner=data["runner"],
|
||||
configs=[PlatformConfig(**c) for c in data["configs"]],
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Output types — shapes of the generated GitHub Actions matrix entries
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Architecture:
|
||||
platform: str
|
||||
runner: list[str]
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class MatrixEntry:
|
||||
"""One entry in the generated build/test strategy matrix."""
|
||||
|
||||
config_name: str
|
||||
cmake_args: str
|
||||
cmake_target: str
|
||||
build_only: 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
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class PackagingEntry:
|
||||
"""One entry in the generated packaging strategy matrix."""
|
||||
|
||||
artifact_name: str
|
||||
image: str
|
||||
distro: str # e.g. "debian" or "rhel"; drives package-format-specific steps
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Matrix expansion
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_ARCHS: dict[str, Architecture] = {
|
||||
"amd64": Architecture(
|
||||
platform="linux/amd64", runner=["self-hosted", "Linux", "X64", "heavy"]
|
||||
),
|
||||
"arm64": Architecture(
|
||||
platform="linux/arm64",
|
||||
runner=["self-hosted", "Linux", "ARM64", "heavy-arm64"],
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def expand_linux_matrix(
|
||||
linux: LinuxFile, event: str | None = None
|
||||
) -> list[MatrixEntry]:
|
||||
"""Expand a LinuxFile into a flat list of matrix entries.
|
||||
|
||||
Each config entry is expanded over the cross-product of its
|
||||
compiler, build_type, sanitizers, and architecture lists. Configs that
|
||||
exclude the current event are skipped.
|
||||
"""
|
||||
entries: list[MatrixEntry] = []
|
||||
|
||||
for distro, configs in linux.configs.items():
|
||||
for cfg in configs:
|
||||
if not runs_on_event(cfg.exclude_event_types, event):
|
||||
continue
|
||||
# An empty sanitizers list means "one entry with no sanitizer".
|
||||
effective_sanitizers = cfg.sanitizers or [""]
|
||||
effective_archs = {arch: _ARCHS[arch] for arch in cfg.arch}
|
||||
|
||||
for compiler, build_type, sanitizer, (arch, arch_info) in itertools.product(
|
||||
cfg.compiler,
|
||||
cfg.build_type,
|
||||
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)
|
||||
|
||||
entries.append(
|
||||
MatrixEntry(
|
||||
config_name=name,
|
||||
image=f"ghcr.io/xrplf/xrpld/nix-{distro}:{linux.image_tag}",
|
||||
cmake_args=get_cmake_args(build_type, cfg.extra_cmake_args),
|
||||
cmake_target="all",
|
||||
build_only=False,
|
||||
build_type=build_type,
|
||||
architecture=arch_info,
|
||||
sanitizers=sanitizer,
|
||||
compiler=compiler,
|
||||
)
|
||||
)
|
||||
|
||||
return entries
|
||||
|
||||
|
||||
def expand_linux_packaging(linux: LinuxFile) -> list[PackagingEntry]:
|
||||
"""Generate the packaging matrix from a LinuxFile's package_configs section.
|
||||
|
||||
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'.
|
||||
"""
|
||||
entries = []
|
||||
for distro, configs in linux.package_configs.items():
|
||||
for cfg in configs:
|
||||
for compiler, build_type in itertools.product(cfg.compiler, cfg.build_type):
|
||||
entries.append(
|
||||
PackagingEntry(
|
||||
artifact_name=f"xrpld-{distro}-{compiler}-{build_type.lower()}-amd64",
|
||||
image=cfg.image,
|
||||
distro=distro,
|
||||
)
|
||||
)
|
||||
|
||||
return entries
|
||||
|
||||
|
||||
def expand_platform_matrix(
|
||||
pf: PlatformFile, event: str | None = None
|
||||
) -> list[MatrixEntry]:
|
||||
"""Expand a PlatformFile (macOS or Windows) into matrix entries.
|
||||
|
||||
Configs that exclude the current event are skipped.
|
||||
"""
|
||||
platform_name, arch = pf.platform.split("/")
|
||||
is_windows = platform_name == "windows"
|
||||
|
||||
entries: list[MatrixEntry] = []
|
||||
for cfg in pf.configs:
|
||||
if not runs_on_event(cfg.exclude_event_types, event):
|
||||
continue
|
||||
for build_type in cfg.build_type:
|
||||
entries.append(
|
||||
MatrixEntry(
|
||||
config_name=f"{platform_name}-{arch}-{build_type.lower()}",
|
||||
cmake_args=get_cmake_args(build_type, cfg.extra_cmake_args),
|
||||
cmake_target="install" if is_windows else "all",
|
||||
build_only=cfg.build_only,
|
||||
build_type=build_type,
|
||||
architecture=Architecture(platform=pf.platform, runner=pf.runner),
|
||||
sanitizers="",
|
||||
)
|
||||
else:
|
||||
configurations.append(
|
||||
{
|
||||
"config_name": config_name,
|
||||
"cmake_args": cmake_args,
|
||||
"cmake_target": cmake_target,
|
||||
"build_only": build_only,
|
||||
"build_type": build_type,
|
||||
"os": os,
|
||||
"architecture": architecture,
|
||||
"sanitizers": "",
|
||||
}
|
||||
)
|
||||
|
||||
return configurations
|
||||
return entries
|
||||
|
||||
|
||||
def read_config(file: Path) -> Config:
|
||||
config = json.loads(file.read_text())
|
||||
if (
|
||||
config["architecture"] is None
|
||||
or config["os"] is None
|
||||
or config["build_type"] is None
|
||||
or config["cmake_args"] is None
|
||||
):
|
||||
raise Exception("Invalid configuration file.")
|
||||
|
||||
return Config(**config)
|
||||
# ---------------------------------------------------------------------------
|
||||
# Entry point
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"-a",
|
||||
"--all",
|
||||
help="Set to generate all configurations (generally used when merging a PR) or leave unset to generate a subset of configurations (generally used when committing to a PR).",
|
||||
action="store_true",
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate a CI strategy matrix for all platforms or a specific one."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--config",
|
||||
help="Path to the JSON file containing the strategy matrix configurations.",
|
||||
required=False,
|
||||
type=Path,
|
||||
help="Platform to generate for ('linux', 'macos', or 'windows'). Defaults to all platforms.",
|
||||
choices=["linux", "macos", "windows"],
|
||||
default=None,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--packaging",
|
||||
help="Emit the Linux packaging matrix instead of the build/test matrix.",
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--event",
|
||||
help="The GitHub event name that triggered the workflow (e.g. 'push', "
|
||||
"'pull_request'). Configs are filtered by their 'event_type'. If "
|
||||
"omitted, no filtering is applied.",
|
||||
default=None,
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
matrix = []
|
||||
if args.config is None or args.config == "":
|
||||
matrix += generate_strategy_matrix(
|
||||
args.all, read_config(THIS_DIR / "linux.json")
|
||||
)
|
||||
matrix += generate_strategy_matrix(
|
||||
args.all, read_config(THIS_DIR / "macos.json")
|
||||
)
|
||||
matrix += generate_strategy_matrix(
|
||||
args.all, read_config(THIS_DIR / "windows.json")
|
||||
)
|
||||
else:
|
||||
matrix += generate_strategy_matrix(args.all, read_config(args.config))
|
||||
matrix: list[MatrixEntry] | list[PackagingEntry] = []
|
||||
|
||||
# Generate the strategy matrix.
|
||||
print(f"matrix={json.dumps({'include': matrix})}")
|
||||
if args.packaging:
|
||||
matrix = expand_linux_packaging(LinuxFile.load(THIS_DIR / "linux.json"))
|
||||
else:
|
||||
if args.config in ("linux", None):
|
||||
matrix += expand_linux_matrix(
|
||||
LinuxFile.load(THIS_DIR / "linux.json"), args.event
|
||||
)
|
||||
if args.config in ("macos", None):
|
||||
matrix += expand_platform_matrix(
|
||||
PlatformFile.load(THIS_DIR / "macos.json"), args.event
|
||||
)
|
||||
if args.config in ("windows", None):
|
||||
matrix += expand_platform_matrix(
|
||||
PlatformFile.load(THIS_DIR / "windows.json"), args.event
|
||||
)
|
||||
|
||||
print(f"matrix={json.dumps({'include': [dataclasses.asdict(e) for e in matrix]})}")
|
||||
|
||||
292
.github/scripts/strategy-matrix/linux.json
vendored
292
.github/scripts/strategy-matrix/linux.json
vendored
@@ -1,212 +1,84 @@
|
||||
{
|
||||
"architecture": [
|
||||
{
|
||||
"platform": "linux/amd64",
|
||||
"runner": ["self-hosted", "Linux", "X64", "heavy"]
|
||||
},
|
||||
{
|
||||
"platform": "linux/arm64",
|
||||
"runner": ["self-hosted", "Linux", "ARM64", "heavy-arm64"]
|
||||
}
|
||||
],
|
||||
"os": [
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "12",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "13",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "14",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "15",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "16",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "17",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "18",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "19",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "20",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "trixie",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "14",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "trixie",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "15",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "trixie",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "20",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "trixie",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "21",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "8",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "14",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "8",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "any",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "9",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "12",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "9",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "13",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "9",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "14",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "9",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "any",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "10",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "14",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "10",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "any",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
"distro_version": "jammy",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "12",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
"distro_version": "noble",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "13",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
"distro_version": "noble",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "14",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
"distro_version": "noble",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "16",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
"distro_version": "noble",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "17",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
"distro_version": "noble",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "18",
|
||||
"image_sha": "ab4d1f0"
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
"distro_version": "noble",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "19",
|
||||
"image_sha": "ab4d1f0"
|
||||
}
|
||||
],
|
||||
"build_type": ["Debug", "Release"],
|
||||
"cmake_args": [""]
|
||||
"image_tag": "sha-e29b523",
|
||||
"configs": {
|
||||
"ubuntu": [
|
||||
{
|
||||
"compiler": ["gcc", "clang"],
|
||||
"build_type": ["Debug", "Release"],
|
||||
"arch": ["amd64", "arm64"]
|
||||
},
|
||||
|
||||
{
|
||||
"compiler": ["gcc", "clang"],
|
||||
"build_type": ["Debug", "Release"],
|
||||
"arch": ["amd64"],
|
||||
"sanitizers": ["address", "undefinedbehavior"]
|
||||
},
|
||||
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Debug"],
|
||||
"arch": ["amd64"],
|
||||
"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"],
|
||||
"arch": ["amd64"],
|
||||
"suffix": "voidstar",
|
||||
"extra_cmake_args": "-Dvoidstar=ON"
|
||||
},
|
||||
{
|
||||
"compiler": ["clang"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"suffix": "reffee",
|
||||
"extra_cmake_args": "-DUNIT_TEST_REFERENCE_FEE=1000"
|
||||
},
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Debug"],
|
||||
"arch": ["amd64"],
|
||||
"suffix": "unity",
|
||||
"extra_cmake_args": "-Dunity=ON",
|
||||
"exclude_event_types": ["pull_request"]
|
||||
}
|
||||
],
|
||||
|
||||
"debian": [
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"]
|
||||
}
|
||||
],
|
||||
|
||||
"rhel": [
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"package_configs": {
|
||||
"debian": [
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-577d745"
|
||||
}
|
||||
],
|
||||
|
||||
"rhel": [
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-577d745"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
25
.github/scripts/strategy-matrix/macos.json
vendored
25
.github/scripts/strategy-matrix/macos.json
vendored
@@ -1,19 +1,16 @@
|
||||
{
|
||||
"architecture": [
|
||||
"platform": "macos/arm64",
|
||||
"runner": ["self-hosted", "macOS", "ARM64", "mac-runner-m1"],
|
||||
"configs": [
|
||||
{
|
||||
"platform": "macos/arm64",
|
||||
"runner": ["self-hosted", "macOS", "ARM64", "mac-runner-m1"]
|
||||
}
|
||||
],
|
||||
"os": [
|
||||
"build_type": "Release",
|
||||
"extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5"
|
||||
},
|
||||
{
|
||||
"distro_name": "macos",
|
||||
"distro_version": "",
|
||||
"compiler_name": "",
|
||||
"compiler_version": "",
|
||||
"image_sha": ""
|
||||
"build_type": "Debug",
|
||||
"extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
|
||||
"build_only": true,
|
||||
"exclude_event_types": ["pull_request"]
|
||||
}
|
||||
],
|
||||
"build_type": ["Debug", "Release"],
|
||||
"cmake_args": ["-DCMAKE_POLICY_VERSION_MINIMUM=3.5"]
|
||||
]
|
||||
}
|
||||
|
||||
23
.github/scripts/strategy-matrix/windows.json
vendored
23
.github/scripts/strategy-matrix/windows.json
vendored
@@ -1,19 +1,12 @@
|
||||
{
|
||||
"architecture": [
|
||||
"platform": "windows/amd64",
|
||||
"runner": ["self-hosted", "Windows", "dev-box-windows-2026"],
|
||||
"configs": [
|
||||
{ "build_type": "Release" },
|
||||
{
|
||||
"platform": "windows/amd64",
|
||||
"runner": ["self-hosted", "Windows", "devbox"]
|
||||
"build_type": "Debug",
|
||||
"build_only": true,
|
||||
"exclude_event_types": ["pull_request"]
|
||||
}
|
||||
],
|
||||
"os": [
|
||||
{
|
||||
"distro_name": "windows",
|
||||
"distro_version": "",
|
||||
"compiler_name": "",
|
||||
"compiler_version": "",
|
||||
"image_sha": ""
|
||||
}
|
||||
],
|
||||
"build_type": ["Debug", "Release"],
|
||||
"cmake_args": [""]
|
||||
]
|
||||
}
|
||||
|
||||
62
.github/workflows/build-nix-images.yml
vendored
Normal file
62
.github/workflows/build-nix-images.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
name: Build Nix Docker images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
paths:
|
||||
- ".github/workflows/build-nix-images.yml"
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "nix/**"
|
||||
- "!nix/docker/README.md"
|
||||
- "!nix/devshell.nix"
|
||||
- "bin/check-tools.sh"
|
||||
- "bin/install-sanitizer-libs.sh"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/build-nix-images.yml"
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "nix/**"
|
||||
- "!nix/docker/README.md"
|
||||
- "!nix/devshell.nix"
|
||||
- "bin/check-tools.sh"
|
||||
- "bin/install-sanitizer-libs.sh"
|
||||
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 nix-${{ matrix.distro.name }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# The base images are the oldest supported version of each distro
|
||||
# that we want to build images for.
|
||||
distro:
|
||||
- name: nixos
|
||||
base_image: nixos/nix:latest
|
||||
- name: ubuntu
|
||||
base_image: ubuntu:20.04
|
||||
- name: debian
|
||||
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
|
||||
with:
|
||||
image_name: xrpld/nix-${{ matrix.distro.name }}
|
||||
dockerfile: nix/docker/Dockerfile
|
||||
base_image: ${{ matrix.distro.base_image }}
|
||||
push: ${{ github.event_name == 'push' }}
|
||||
46
.github/workflows/build-packaging-images.yml
vendored
Normal file
46
.github/workflows/build-packaging-images.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: Build packaging Docker images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
paths:
|
||||
- ".github/workflows/build-packaging-images.yml"
|
||||
- "package/Dockerfile"
|
||||
- "package/install-packaging-tools.sh"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/build-packaging-images.yml"
|
||||
- "package/Dockerfile"
|
||||
- "package/install-packaging-tools.sh"
|
||||
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 packaging-${{ matrix.distro.name }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
distro:
|
||||
- name: debian
|
||||
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
|
||||
with:
|
||||
image_name: xrpld/packaging-${{ matrix.distro.name }}
|
||||
dockerfile: package/Dockerfile
|
||||
base_image: ${{ matrix.distro.base_image }}
|
||||
push: ${{ github.event_name == 'push' }}
|
||||
4
.github/workflows/check-pr-commits.yml
vendored
4
.github/workflows/check-pr-commits.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Check PR commits
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
|
||||
# The action needs to have write permissions to post comments on the PR.
|
||||
permissions:
|
||||
@@ -10,4 +10,4 @@ permissions:
|
||||
|
||||
jobs:
|
||||
check_commits:
|
||||
uses: XRPLF/actions/.github/workflows/check-pr-commits.yml@481048b78b94ac3343d1292b4ef125a813879f2b
|
||||
uses: XRPLF/actions/.github/workflows/check-pr-commits.yml@e2c7f400d1e85ae65dad552fd425169fbacca4a3
|
||||
|
||||
39
.github/workflows/check-pr-description.yml
vendored
Normal file
39
.github/workflows/check-pr-description.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: Check PR description
|
||||
|
||||
on:
|
||||
merge_group:
|
||||
types:
|
||||
- checks_requested
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
branches:
|
||||
- develop
|
||||
- "release-*"
|
||||
- "release/*"
|
||||
- "staging/*"
|
||||
|
||||
jobs:
|
||||
check_description:
|
||||
if: ${{ github.event.pull_request.draft != true }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Write PR body to file
|
||||
env:
|
||||
PR_BODY: ${{ github.event.pull_request.body }}
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: printenv PR_BODY >pr_body.md
|
||||
|
||||
- name: Check PR description differs from template
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: |
|
||||
python .github/scripts/check-pr-description.py \
|
||||
--template-file .github/pull_request_template.md \
|
||||
--pr-body-file pr_body.md
|
||||
15
.github/workflows/check-pr-title.yml
vendored
15
.github/workflows/check-pr-title.yml
vendored
@@ -5,10 +5,19 @@ on:
|
||||
types:
|
||||
- checks_requested
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize, ready_for_review]
|
||||
branches: [develop]
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
branches:
|
||||
- develop
|
||||
- "release-*"
|
||||
- "release/*"
|
||||
- "staging/*"
|
||||
|
||||
jobs:
|
||||
check_title:
|
||||
if: ${{ github.event.pull_request.draft != true }}
|
||||
uses: XRPLF/actions/.github/workflows/check-pr-title.yml@e2c7f400d1e85ae65dad552fd425169fbacca4a3
|
||||
uses: XRPLF/actions/.github/workflows/check-pr-title.yml@cba1f0891650baf1a9c88624dc2d72573be2eb81
|
||||
|
||||
2
.github/workflows/conflicting-pr.yml
vendored
2
.github/workflows/conflicting-pr.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check if PRs are dirty
|
||||
uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3
|
||||
uses: eps1lon/actions-label-merge-conflict@0273be72a0bbd58fcd71d0d6c02c209b50d1e5e1 # v3.1.0
|
||||
with:
|
||||
dirtyLabel: "PR: has conflicts"
|
||||
repoToken: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
43
.github/workflows/on-pr.yml
vendored
43
.github/workflows/on-pr.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- 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
|
||||
@@ -46,32 +46,34 @@ jobs:
|
||||
# that Github considers any skipped jobs to have passed, and in
|
||||
# turn the required checks as well.
|
||||
id: changes
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
# These paths are unique to `on-pr.yml`.
|
||||
.github/scripts/levelization/**
|
||||
.github/scripts/otel-naming/**
|
||||
.github/scripts/rename/**
|
||||
.github/workflows/reusable-check-levelization.yml
|
||||
.github/workflows/reusable-check-otel-naming.yml
|
||||
.github/workflows/reusable-check-rename.yml
|
||||
.github/workflows/on-pr.yml
|
||||
|
||||
# Keep the paths below in sync with those in `on-trigger.yml`.
|
||||
.github/actions/build-deps/**
|
||||
.github/actions/build-test/**
|
||||
.github/actions/generate-version/**
|
||||
.github/actions/setup-conan/**
|
||||
.github/scripts/strategy-matrix/**
|
||||
.github/workflows/reusable-build.yml
|
||||
.github/workflows/reusable-build-test-config.yml
|
||||
.github/workflows/reusable-build-test.yml
|
||||
.github/workflows/reusable-clang-tidy.yml
|
||||
.github/workflows/reusable-clang-tidy-files.yml
|
||||
.github/workflows/reusable-package.yml
|
||||
.github/workflows/reusable-strategy-matrix.yml
|
||||
.github/workflows/reusable-test.yml
|
||||
.github/workflows/reusable-upload-recipe.yml
|
||||
.clang-tidy
|
||||
.codecov.yml
|
||||
bin/check-tools.sh
|
||||
cfg/**
|
||||
cmake/**
|
||||
conan/**
|
||||
external/**
|
||||
@@ -81,6 +83,10 @@ jobs:
|
||||
CMakeLists.txt
|
||||
conanfile.py
|
||||
conan.lock
|
||||
LICENSE.md
|
||||
package/**
|
||||
README.md
|
||||
|
||||
- name: Check whether to run
|
||||
# This step determines whether the rest of the workflow should
|
||||
# run. The rest of the workflow will run if this job runs AND at
|
||||
@@ -95,7 +101,7 @@ jobs:
|
||||
READY: ${{ contains(github.event.pull_request.labels.*.name, 'Ready to merge') }}
|
||||
MERGE: ${{ github.event_name == 'merge_group' }}
|
||||
run: |
|
||||
echo "go=${{ (env.DRAFT != 'true' && env.READY == 'true') || env.FILES == 'true' || env.MERGE == 'true' }}" >> "${GITHUB_OUTPUT}"
|
||||
echo "go=${{ (env.DRAFT != 'true' && env.READY == 'true') || env.FILES == 'true' || env.MERGE == 'true' }}" >>"${GITHUB_OUTPUT}"
|
||||
cat "${GITHUB_OUTPUT}"
|
||||
outputs:
|
||||
go: ${{ steps.go.outputs.go == 'true' }}
|
||||
@@ -105,6 +111,11 @@ jobs:
|
||||
if: ${{ needs.should-run.outputs.go == 'true' }}
|
||||
uses: ./.github/workflows/reusable-check-levelization.yml
|
||||
|
||||
check-otel-naming:
|
||||
needs: should-run
|
||||
if: ${{ needs.should-run.outputs.go == 'true' }}
|
||||
uses: ./.github/workflows/reusable-check-otel-naming.yml
|
||||
|
||||
check-rename:
|
||||
needs: should-run
|
||||
if: ${{ needs.should-run.outputs.go == 'true' }}
|
||||
@@ -118,7 +129,6 @@ jobs:
|
||||
issues: write
|
||||
contents: read
|
||||
with:
|
||||
check_only_changed: true
|
||||
create_issue_on_failure: false
|
||||
|
||||
build-test:
|
||||
@@ -137,6 +147,11 @@ jobs:
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
package:
|
||||
needs: [should-run, build-test]
|
||||
if: ${{ needs.should-run.outputs.go == 'true' }}
|
||||
uses: ./.github/workflows/reusable-package.yml
|
||||
|
||||
upload-recipe:
|
||||
needs:
|
||||
- should-run
|
||||
@@ -145,8 +160,8 @@ jobs:
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && needs.should-run.outputs.go == 'true' && github.event_name == 'pull_request' && startsWith(github.event.pull_request.base.ref, 'release') }}
|
||||
uses: ./.github/workflows/reusable-upload-recipe.yml
|
||||
secrets:
|
||||
remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }}
|
||||
remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }}
|
||||
remote_username: ${{ secrets.NEXUS_REMOTE_USERNAME }}
|
||||
remote_password: ${{ secrets.NEXUS_REMOTE_PASSWORD }}
|
||||
|
||||
notify-clio:
|
||||
needs: upload-recipe
|
||||
@@ -160,20 +175,22 @@ jobs:
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
run: |
|
||||
gh api --method POST -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
/repos/xrplf/clio/dispatches -f "event_type=check_libxrpl" \
|
||||
-F "client_payload[ref]=${{ needs.upload-recipe.outputs.recipe_ref }}" \
|
||||
-F "client_payload[pr_url]=${PR_URL}"
|
||||
/repos/xrplf/clio/dispatches -f "event_type=check_libxrpl" \
|
||||
-F "client_payload[ref]=${{ needs.upload-recipe.outputs.recipe_ref }}" \
|
||||
-F "client_payload[pr_url]=${PR_URL}"
|
||||
|
||||
passed:
|
||||
if: failure() || cancelled()
|
||||
needs:
|
||||
- check-levelization
|
||||
- check-otel-naming
|
||||
- check-rename
|
||||
- clang-tidy
|
||||
- build-test
|
||||
- package
|
||||
- upload-recipe
|
||||
- notify-clio
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Fail
|
||||
run: false
|
||||
run: exit 1
|
||||
|
||||
26
.github/workflows/on-tag.yml
vendored
26
.github/workflows/on-tag.yml
vendored
@@ -1,5 +1,5 @@
|
||||
# This workflow uploads the libxrpl recipe to the Conan remote when a versioned
|
||||
# tag is pushed.
|
||||
# This workflow uploads the libxrpl recipe to the Conan remote and builds
|
||||
# release packages when a versioned tag is pushed.
|
||||
name: Tag
|
||||
|
||||
on:
|
||||
@@ -20,5 +20,23 @@ jobs:
|
||||
if: ${{ github.repository == 'XRPLF/rippled' }}
|
||||
uses: ./.github/workflows/reusable-upload-recipe.yml
|
||||
secrets:
|
||||
remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }}
|
||||
remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }}
|
||||
remote_username: ${{ secrets.NEXUS_REMOTE_USERNAME }}
|
||||
remote_password: ${{ secrets.NEXUS_REMOTE_PASSWORD }}
|
||||
|
||||
build-test:
|
||||
if: ${{ github.repository == 'XRPLF/rippled' }}
|
||||
uses: ./.github/workflows/reusable-build-test.yml
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [linux]
|
||||
with:
|
||||
ccache_enabled: false
|
||||
os: ${{ matrix.os }}
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
package:
|
||||
if: ${{ github.repository == 'XRPLF/rippled' }}
|
||||
needs: build-test
|
||||
uses: ./.github/workflows/reusable-package.yml
|
||||
|
||||
19
.github/workflows/on-trigger.yml
vendored
19
.github/workflows/on-trigger.yml
vendored
@@ -15,20 +15,20 @@ on:
|
||||
|
||||
# Keep the paths below in sync with those in `on-pr.yml`.
|
||||
- ".github/actions/build-deps/**"
|
||||
- ".github/actions/build-test/**"
|
||||
- ".github/actions/generate-version/**"
|
||||
- ".github/actions/setup-conan/**"
|
||||
- ".github/scripts/strategy-matrix/**"
|
||||
- ".github/workflows/reusable-build.yml"
|
||||
- ".github/workflows/reusable-build-test-config.yml"
|
||||
- ".github/workflows/reusable-build-test.yml"
|
||||
- ".github/workflows/reusable-clang-tidy.yml"
|
||||
- ".github/workflows/reusable-clang-tidy-files.yml"
|
||||
- ".github/workflows/reusable-package.yml"
|
||||
- ".github/workflows/reusable-strategy-matrix.yml"
|
||||
- ".github/workflows/reusable-test.yml"
|
||||
- ".github/workflows/reusable-upload-recipe.yml"
|
||||
- ".clang-tidy"
|
||||
- ".codecov.yml"
|
||||
- "bin/check-tools.sh"
|
||||
- "cfg/**"
|
||||
- "cmake/**"
|
||||
- "conan/**"
|
||||
- "external/**"
|
||||
@@ -38,6 +38,9 @@ on:
|
||||
- "CMakeLists.txt"
|
||||
- "conanfile.py"
|
||||
- "conan.lock"
|
||||
- "LICENSE.md"
|
||||
- "package/**"
|
||||
- "README.md"
|
||||
|
||||
# Run at 06:32 UTC on every day of the week from Monday through Friday. This
|
||||
# will force all dependencies to be rebuilt, which is useful to verify that
|
||||
@@ -69,7 +72,6 @@ jobs:
|
||||
issues: write
|
||||
contents: read
|
||||
with:
|
||||
check_only_changed: false
|
||||
create_issue_on_failure: ${{ github.event_name == 'schedule' }}
|
||||
|
||||
build-test:
|
||||
@@ -86,7 +88,6 @@ jobs:
|
||||
# not identical to a regular compilation.
|
||||
ccache_enabled: ${{ github.repository_owner == 'XRPLF' && !startsWith(github.ref, 'refs/heads/release') }}
|
||||
os: ${{ matrix.os }}
|
||||
strategy_matrix: ${{ github.event_name == 'schedule' && 'all' || 'minimal' }}
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -96,5 +97,9 @@ jobs:
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' && github.ref == 'refs/heads/develop' }}
|
||||
uses: ./.github/workflows/reusable-upload-recipe.yml
|
||||
secrets:
|
||||
remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }}
|
||||
remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }}
|
||||
remote_username: ${{ secrets.NEXUS_REMOTE_USERNAME }}
|
||||
remote_password: ${{ secrets.NEXUS_REMOTE_PASSWORD }}
|
||||
|
||||
package:
|
||||
needs: build-test
|
||||
uses: ./.github/workflows/reusable-package.yml
|
||||
|
||||
2
.github/workflows/pre-commit.yml
vendored
2
.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@e7896f15cc60d0da1a272c77ee5c4026b424f9c7
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@e06d4138c9ec8dceeb7c818645faa38087ea9e3d
|
||||
with:
|
||||
runs_on: ubuntu-latest
|
||||
container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }'
|
||||
|
||||
33
.github/workflows/publish-docs.yml
vendored
33
.github/workflows/publish-docs.yml
vendored
@@ -6,7 +6,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "develop"
|
||||
- "release*"
|
||||
paths:
|
||||
- ".github/workflows/publish-docs.yml"
|
||||
- "*.md"
|
||||
@@ -37,18 +36,18 @@ env:
|
||||
BUILD_DIR: build
|
||||
# ubuntu-latest has only 2 CPUs for private repositories
|
||||
# https://docs.github.com/en/actions/reference/runners/github-hosted-runners#standard-github-hosted-runners-for--private-repositories
|
||||
NPROC_SUBTRACT: ${{ github.event.repository.private && '1' || '2' }}
|
||||
NPROC_SUBTRACT: ${{ github.event.repository.visibility == 'public' && '2' || '1' }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/ci/tools-rippled-documentation:sha-a8c7be1
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-e29b523
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@2bbc2dc1abeec7bfaa886804ab86871ac201764e
|
||||
uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
@@ -58,19 +57,11 @@ jobs:
|
||||
with:
|
||||
subtract: ${{ env.NPROC_SUBTRACT }}
|
||||
|
||||
- name: Check configuration
|
||||
run: |
|
||||
echo 'Checking path.'
|
||||
echo ${PATH} | tr ':' '\n'
|
||||
- name: Print build environment
|
||||
uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574
|
||||
|
||||
echo 'Checking environment variables.'
|
||||
env | sort
|
||||
|
||||
echo 'Checking CMake version.'
|
||||
cmake --version
|
||||
|
||||
echo 'Checking Doxygen version.'
|
||||
doxygen --version
|
||||
- name: Check Doxygen version
|
||||
run: doxygen --version
|
||||
|
||||
- name: Build documentation
|
||||
env:
|
||||
@@ -82,13 +73,13 @@ jobs:
|
||||
cmake --build . --target docs --parallel ${BUILD_NPROC}
|
||||
|
||||
- name: Create documentation artifact
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0
|
||||
if: ${{ github.event.repository.visibility == 'public' && github.event_name == 'push' }}
|
||||
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
|
||||
with:
|
||||
path: ${{ env.BUILD_DIR }}/docs/html
|
||||
|
||||
deploy:
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }}
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
@@ -100,4 +91,4 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deploy
|
||||
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
|
||||
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
|
||||
|
||||
229
.github/workflows/reusable-build-test-config.yml
vendored
229
.github/workflows/reusable-build-test-config.yml
vendored
@@ -57,6 +57,12 @@ on:
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
compiler:
|
||||
description: 'The compiler to use ("gcc" or "clang"). Leave empty for macOS/Windows (uses system default).'
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
secrets:
|
||||
CODECOV_TOKEN:
|
||||
description: "The Codecov token to use for uploading coverage reports."
|
||||
@@ -76,7 +82,7 @@ jobs:
|
||||
name: ${{ inputs.config_name }}
|
||||
runs-on: ${{ fromJSON(inputs.runs_on) }}
|
||||
container: ${{ inputs.image != '' && inputs.image || null }}
|
||||
timeout-minutes: ${{ inputs.sanitizers != '' && 360 || 60 }}
|
||||
timeout-minutes: ${{ inputs.sanitizers != '' && 360 || 180 }}
|
||||
env:
|
||||
# Use a namespace to keep the objects separate for each configuration.
|
||||
CCACHE_NAMESPACE: ${{ inputs.config_name }}
|
||||
@@ -104,19 +110,24 @@ jobs:
|
||||
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@2bbc2dc1abeec7bfaa886804ab86871ac201764e
|
||||
uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8
|
||||
with:
|
||||
enable_ccache: ${{ inputs.ccache_enabled }}
|
||||
|
||||
- name: Set ccache log file
|
||||
if: ${{ inputs.ccache_enabled && runner.debug == '1' }}
|
||||
run: echo "CCACHE_LOGFILE=${{ runner.temp }}/ccache.log" >> "${GITHUB_ENV}"
|
||||
run: echo "CCACHE_LOGFILE=${{ runner.temp }}/ccache.log" >>"${GITHUB_ENV}"
|
||||
|
||||
- name: Check tools
|
||||
env:
|
||||
CHECK_TOOLS_SKIP_CLONE: "1"
|
||||
run: ./bin/check-tools.sh
|
||||
|
||||
- name: Print build environment
|
||||
uses: ./.github/actions/print-env
|
||||
uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574
|
||||
|
||||
- name: Get number of processors
|
||||
uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
@@ -124,6 +135,12 @@ jobs:
|
||||
with:
|
||||
subtract: ${{ inputs.nproc_subtract }}
|
||||
|
||||
- name: Set compiler environment (Linux)
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
uses: ./.github/actions/set-compiler-env
|
||||
with:
|
||||
compiler: ${{ inputs.compiler }}
|
||||
|
||||
- name: Setup Conan
|
||||
env:
|
||||
SANITIZERS: ${{ inputs.sanitizers }}
|
||||
@@ -143,15 +160,61 @@ jobs:
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
env:
|
||||
BUILD_TYPE: ${{ inputs.build_type }}
|
||||
SANITIZERS: ${{ inputs.sanitizers }}
|
||||
CMAKE_ARGS: ${{ inputs.cmake_args }}
|
||||
run: |
|
||||
cmake \
|
||||
-G '${{ runner.os == 'Windows' && 'Visual Studio 17 2022' || 'Ninja' }}' \
|
||||
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \
|
||||
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
|
||||
${CMAKE_ARGS} \
|
||||
..
|
||||
-G '${{ runner.os == 'Windows' && 'Visual Studio 18 2026' || 'Ninja' }}' \
|
||||
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \
|
||||
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
|
||||
${CMAKE_ARGS} \
|
||||
..
|
||||
|
||||
# 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.
|
||||
# GITHUB_WORKSPACE (not the github.workspace context) is used so the path
|
||||
# resolves correctly inside the container job.
|
||||
- name: Set sanitizer options
|
||||
if: ${{ !inputs.build_only && env.SANITIZERS_ENABLED == 'true' }}
|
||||
env:
|
||||
CONFIG_NAME: ${{ inputs.config_name }}
|
||||
run: |
|
||||
SUPP="${GITHUB_WORKSPACE}/sanitizers/suppressions"
|
||||
ASAN_OPTS="include=${SUPP}/runtime-asan-options.txt:suppressions=${SUPP}/asan.supp"
|
||||
if [[ "${CONFIG_NAME}" == *gcc* ]]; then
|
||||
ASAN_OPTS="${ASAN_OPTS}:alloc_dealloc_mismatch=0"
|
||||
fi
|
||||
echo "ASAN_OPTIONS=${ASAN_OPTS}" >>${GITHUB_ENV}
|
||||
echo "TSAN_OPTIONS=include=${SUPP}/runtime-tsan-options.txt:suppressions=${SUPP}/tsan.supp" >>${GITHUB_ENV}
|
||||
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 }}
|
||||
@@ -161,62 +224,64 @@ jobs:
|
||||
CMAKE_TARGET: ${{ inputs.cmake_target }}
|
||||
run: |
|
||||
cmake \
|
||||
--build . \
|
||||
--config "${BUILD_TYPE}" \
|
||||
--parallel "${BUILD_NPROC}" \
|
||||
--target "${CMAKE_TARGET}"
|
||||
|
||||
- name: Check protocol autogen files are up-to-date
|
||||
env:
|
||||
MESSAGE: |
|
||||
|
||||
The generated protocol wrapper classes are out of date.
|
||||
|
||||
This typically happens when your branch is behind develop and
|
||||
the macro files or generator scripts have changed.
|
||||
|
||||
To fix this:
|
||||
1. Update your branch from develop (merge or rebase)
|
||||
2. Build with code generation enabled (XRPL_NO_CODEGEN=OFF)
|
||||
3. Commit and push the regenerated files
|
||||
run: |
|
||||
set -e
|
||||
DIFF=$(git 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 diff -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen
|
||||
echo "${MESSAGE}"
|
||||
exit 1
|
||||
fi
|
||||
--build . \
|
||||
--config "${BUILD_TYPE}" \
|
||||
--parallel "${BUILD_NPROC}" \
|
||||
--target "${CMAKE_TARGET}"
|
||||
|
||||
- name: Show ccache statistics
|
||||
if: ${{ inputs.ccache_enabled }}
|
||||
run: |
|
||||
ccache --show-stats -vv
|
||||
if [ '${{ runner.debug }}' = '1' ]; then
|
||||
cat "${CCACHE_LOGFILE}"
|
||||
curl ${CCACHE_REMOTE_STORAGE%|*}/status || true
|
||||
cat "${CCACHE_LOGFILE}"
|
||||
curl ${CCACHE_REMOTE_STORAGE%|*}/status || true
|
||||
fi
|
||||
|
||||
- name: Upload the binary (Linux)
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && runner.os == 'Linux' }}
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
if: ${{ github.event.repository.visibility == 'public' && runner.os == 'Linux' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: xrpld-${{ inputs.config_name }}
|
||||
path: ${{ env.BUILD_DIR }}/xrpld
|
||||
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
|
||||
with:
|
||||
name: xrpl_tests-${{ inputs.config_name }}
|
||||
path: ${{ env.BUILD_DIR }}/xrpl_tests
|
||||
retention-days: 3
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Export server definitions
|
||||
if: ${{ runner.os != 'Windows' && !inputs.build_only && env.VOIDSTAR_ENABLED != 'true' }}
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
run: |
|
||||
set -o pipefail
|
||||
./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' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: server-definitions
|
||||
path: ${{ env.BUILD_DIR }}/server_definitions.json
|
||||
retention-days: 3
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Check linking (Linux)
|
||||
if: ${{ runner.os == 'Linux' && env.SANITIZERS_ENABLED == 'false' }}
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
run: |
|
||||
ldd ./xrpld
|
||||
if [ "$(ldd ./xrpld | grep -E '(libstdc\+\+|libgcc)' | wc -l)" -eq 0 ]; then
|
||||
echo 'The binary is statically linked.'
|
||||
echo 'The binary is statically linked.'
|
||||
else
|
||||
echo 'The binary is dynamically linked.'
|
||||
exit 1
|
||||
echo 'The binary is dynamically linked.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Verify presence of instrumentation (Linux)
|
||||
@@ -225,32 +290,10 @@ jobs:
|
||||
run: |
|
||||
./xrpld --version | grep libvoidstar
|
||||
|
||||
- name: Set sanitizer options
|
||||
if: ${{ !inputs.build_only && env.SANITIZERS_ENABLED == 'true' }}
|
||||
env:
|
||||
CONFIG_NAME: ${{ inputs.config_name }}
|
||||
run: |
|
||||
ASAN_OPTS="include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-asan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/asan.supp"
|
||||
if [[ "${CONFIG_NAME}" == *gcc* ]]; then
|
||||
ASAN_OPTS="${ASAN_OPTS}:alloc_dealloc_mismatch=0"
|
||||
fi
|
||||
echo "ASAN_OPTIONS=${ASAN_OPTS}" >> ${GITHUB_ENV}
|
||||
echo "TSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-tsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/tsan.supp" >> ${GITHUB_ENV}
|
||||
echo "UBSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-ubsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/ubsan.supp" >> ${GITHUB_ENV}
|
||||
echo "LSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-lsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/lsan.supp" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Run the separate tests
|
||||
if: ${{ !inputs.build_only }}
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
# Windows locks some of the build files while running tests, and parallel jobs can collide
|
||||
env:
|
||||
BUILD_TYPE: ${{ inputs.build_type }}
|
||||
PARALLELISM: ${{ runner.os == 'Windows' && '1' || steps.nproc.outputs.nproc }}
|
||||
run: |
|
||||
ctest \
|
||||
--output-on-failure \
|
||||
-C "${BUILD_TYPE}" \
|
||||
-j "${PARALLELISM}"
|
||||
working-directory: ${{ runner.os == 'Windows' && format('{0}/{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }}
|
||||
run: ./xrpl_tests
|
||||
|
||||
- name: Run the embedded tests
|
||||
if: ${{ !inputs.build_only }}
|
||||
@@ -260,20 +303,46 @@ jobs:
|
||||
run: |
|
||||
set -o pipefail
|
||||
# Coverage builds are slower due to instrumentation; use fewer parallel jobs to avoid flakiness
|
||||
[ "$COVERAGE_ENABLED" = "true" ] && BUILD_NPROC=$(( BUILD_NPROC - 2 ))
|
||||
./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee unittest.log
|
||||
[ "$COVERAGE_ENABLED" = "true" ] && BUILD_NPROC=$((BUILD_NPROC - 2))
|
||||
|
||||
# The resolver/preload workaround is only correct for the ASan build:
|
||||
# a regular build doesn't hit the __dn_expand interceptor bug, and must
|
||||
# NOT have libasan injected. So only preload when xrpld is ASan-built.
|
||||
#
|
||||
# libresolv hosts getaddrinfo's resolver helpers (dn_expand, res_*). Under ASan
|
||||
# these are intercepted via dlsym(RTLD_NEXT, ...), which yields a NULL pointer
|
||||
# and crashes DNS resolution if libresolv isn't loaded. Linking it guarantees
|
||||
# the symbols are present; it's a harmless no-op on glibc >= 2.34 (merged into
|
||||
# libc) and is what the compiler driver already does for sanitizer builds.
|
||||
# https://github.com/llvm/llvm-project/issues/59007
|
||||
# https://github.com/google/sanitizers/issues/1592
|
||||
if ldd ./xrpld | grep -q libasan; then
|
||||
PRELOAD="$(gcc -print-file-name=libasan.so):/usr/lib/x86_64-linux-gnu/libresolv.so.2"
|
||||
else
|
||||
PRELOAD=""
|
||||
fi
|
||||
|
||||
LD_PRELOAD="$PRELOAD" ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee unittest.log
|
||||
|
||||
- name: Show test failure summary
|
||||
if: ${{ failure() && !inputs.build_only }}
|
||||
working-directory: ${{ runner.os == 'Windows' && format('{0}/{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }}
|
||||
env:
|
||||
WORKING_DIR: ${{ runner.os == 'Windows' && format('{0}\{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }}
|
||||
run: |
|
||||
if [ ! -d "${WORKING_DIR}" ]; then
|
||||
echo "Working directory '${WORKING_DIR}' does not exist."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cd "${WORKING_DIR}"
|
||||
|
||||
if [ ! -f unittest.log ]; then
|
||||
echo "unittest.log not found; embedded tests may not have run."
|
||||
exit 0
|
||||
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."
|
||||
echo "Log present but no failure lines found in unittest.log."
|
||||
fi
|
||||
- name: Debug failure (Linux)
|
||||
if: ${{ failure() && runner.os == 'Linux' && !inputs.build_only }}
|
||||
@@ -291,14 +360,14 @@ jobs:
|
||||
BUILD_TYPE: ${{ inputs.build_type }}
|
||||
run: |
|
||||
cmake \
|
||||
--build . \
|
||||
--config "${BUILD_TYPE}" \
|
||||
--parallel "${BUILD_NPROC}" \
|
||||
--target coverage
|
||||
--build . \
|
||||
--config "${BUILD_TYPE}" \
|
||||
--parallel "${BUILD_NPROC}" \
|
||||
--target coverage
|
||||
|
||||
- name: Upload coverage report
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
|
||||
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5.5.3
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
with:
|
||||
disable_search: true
|
||||
disable_telem: true
|
||||
|
||||
12
.github/workflows/reusable-build-test.yml
vendored
12
.github/workflows/reusable-build-test.yml
vendored
@@ -19,13 +19,6 @@ on:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
strategy_matrix:
|
||||
# TODO: Support additional strategies, e.g. "ubuntu" for generating all Ubuntu configurations.
|
||||
description: 'The strategy matrix to use for generating the configurations ("minimal", "all").'
|
||||
required: false
|
||||
type: string
|
||||
default: "minimal"
|
||||
|
||||
secrets:
|
||||
CODECOV_TOKEN:
|
||||
description: "The Codecov token to use for uploading coverage reports."
|
||||
@@ -37,7 +30,6 @@ jobs:
|
||||
uses: ./.github/workflows/reusable-strategy-matrix.yml
|
||||
with:
|
||||
os: ${{ inputs.os }}
|
||||
strategy_matrix: ${{ inputs.strategy_matrix }}
|
||||
|
||||
# Build and test the binary for each configuration.
|
||||
build-test-config:
|
||||
@@ -47,7 +39,6 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: ${{ github.event_name == 'merge_group' }}
|
||||
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
|
||||
max-parallel: 10
|
||||
with:
|
||||
build_only: ${{ matrix.build_only }}
|
||||
build_type: ${{ matrix.build_type }}
|
||||
@@ -55,8 +46,9 @@ jobs:
|
||||
cmake_args: ${{ matrix.cmake_args }}
|
||||
cmake_target: ${{ matrix.cmake_target }}
|
||||
runs_on: ${{ toJSON(matrix.architecture.runner) }}
|
||||
image: ${{ contains(matrix.architecture.platform, 'linux') && format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}-sha-{4}', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version, matrix.os.image_sha) || '' }}
|
||||
image: ${{ matrix.image || '' }}
|
||||
config_name: ${{ matrix.config_name }}
|
||||
sanitizers: ${{ matrix.sanitizers }}
|
||||
compiler: ${{ matrix.compiler || '' }}
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Check levelization
|
||||
run: python .github/scripts/levelization/generate.py
|
||||
- name: Check for differences
|
||||
@@ -38,9 +38,9 @@ jobs:
|
||||
run: |
|
||||
DIFF=$(git status --porcelain)
|
||||
if [ -n "${DIFF}" ]; then
|
||||
# Print the differences to give the contributor a hint about what to
|
||||
# expect when running levelization on their own machine.
|
||||
git diff
|
||||
echo "${MESSAGE}"
|
||||
exit 1
|
||||
# Print the differences to give the contributor a hint about what to
|
||||
# expect when running levelization on their own machine.
|
||||
git diff
|
||||
echo "${MESSAGE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
28
.github/workflows/reusable-check-otel-naming.yml
vendored
Normal file
28
.github/workflows/reusable-check-otel-naming.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
# This workflow checks that OpenTelemetry span-attribute names stay consistent
|
||||
# across the code (*SpanNames.h), collector, Tempo, dashboards, and docs.
|
||||
# See .github/scripts/otel-naming/check_otel_naming.py and the
|
||||
# "Telemetry span attribute naming" section in CONTRIBUTING.md.
|
||||
name: Check OTel naming
|
||||
|
||||
# This workflow can only be triggered by other workflows.
|
||||
on: workflow_call
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-otel-naming
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
otel-naming:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- name: Check OTel naming
|
||||
# The script is stdlib-only and reads only files already in the tree;
|
||||
# it enforces each rule only when the layer it needs is present, so it
|
||||
# works whether telemetry changes land in one PR or several.
|
||||
run: python .github/scripts/otel-naming/check_otel_naming.py
|
||||
14
.github/workflows/reusable-check-rename.yml
vendored
14
.github/workflows/reusable-check-rename.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Check definitions
|
||||
run: .github/scripts/rename/definitions.sh .
|
||||
- name: Check copyright notices
|
||||
@@ -33,6 +33,8 @@ jobs:
|
||||
run: .github/scripts/rename/config.sh .
|
||||
- name: Check include guards
|
||||
run: .github/scripts/rename/include.sh .
|
||||
- name: Check documentation
|
||||
run: .github/scripts/rename/docs.sh .
|
||||
- name: Check for differences
|
||||
env:
|
||||
MESSAGE: |
|
||||
@@ -46,9 +48,9 @@ jobs:
|
||||
run: |
|
||||
DIFF=$(git status --porcelain)
|
||||
if [ -n "${DIFF}" ]; then
|
||||
# Print the differences to give the contributor a hint about what to
|
||||
# expect when running the renaming scripts on their own machine.
|
||||
git diff
|
||||
echo "${MESSAGE}"
|
||||
exit 1
|
||||
# Print the differences to give the contributor a hint about what to
|
||||
# expect when running the renaming scripts on their own machine.
|
||||
git diff
|
||||
echo "${MESSAGE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
162
.github/workflows/reusable-clang-tidy-files.yml
vendored
162
.github/workflows/reusable-clang-tidy-files.yml
vendored
@@ -1,162 +0,0 @@
|
||||
name: Run clang-tidy on files
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
files:
|
||||
description: "List of files to check (empty means check all files)"
|
||||
type: string
|
||||
default: ""
|
||||
create_issue_on_failure:
|
||||
description: "Whether to create an issue if the check failed"
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
# Conan installs the generators in the build/generators directory, see the
|
||||
# layout() method in conanfile.py. We then run CMake from the build directory.
|
||||
BUILD_DIR: build
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
run-clang-tidy:
|
||||
name: Run clang tidy
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: "ghcr.io/xrplf/ci/debian-trixie:clang-21-sha-53033a2"
|
||||
permissions:
|
||||
issues: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@2bbc2dc1abeec7bfaa886804ab86871ac201764e
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
- name: Print build environment
|
||||
uses: ./.github/actions/print-env
|
||||
|
||||
- name: Get number of processors
|
||||
uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
id: nproc
|
||||
|
||||
- name: Setup Conan
|
||||
uses: ./.github/actions/setup-conan
|
||||
|
||||
- name: Build dependencies
|
||||
uses: ./.github/actions/build-deps
|
||||
with:
|
||||
build_nproc: ${{ steps.nproc.outputs.nproc }}
|
||||
build_type: ${{ env.BUILD_TYPE }}
|
||||
log_verbosity: verbose
|
||||
|
||||
- name: Configure CMake
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
run: |
|
||||
cmake \
|
||||
-G 'Ninja' \
|
||||
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \
|
||||
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
|
||||
-Dtests=ON \
|
||||
-Dwerr=ON \
|
||||
-Dxrpld=ON \
|
||||
..
|
||||
|
||||
# clang-tidy needs headers generated from proto files
|
||||
- name: Build libxrpl.libpb
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
run: |
|
||||
ninja -j ${{ steps.nproc.outputs.nproc }} xrpl.libpb
|
||||
|
||||
- name: Run clang tidy
|
||||
id: run_clang_tidy
|
||||
continue-on-error: true
|
||||
env:
|
||||
TARGETS: ${{ inputs.files != '' && inputs.files || 'src tests' }}
|
||||
run: |
|
||||
run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "${BUILD_DIR}" ${TARGETS} 2>&1 | tee clang-tidy-output.txt
|
||||
|
||||
- name: Upload clang-tidy output
|
||||
if: steps.run_clang_tidy.outcome != 'success'
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: clang-tidy-results
|
||||
path: clang-tidy-output.txt
|
||||
retention-days: 30
|
||||
|
||||
- name: Create an issue
|
||||
if: steps.run_clang_tidy.outcome != 'success' && inputs.create_issue_on_failure
|
||||
id: create_issue
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
# Prepare issue body with clang-tidy output
|
||||
cat > issue.md <<EOF
|
||||
## Clang-tidy Check Failed
|
||||
|
||||
**Workflow:** ${{ github.workflow }}
|
||||
**Run ID:** ${{ github.run_id }}
|
||||
**Commit:** ${{ github.sha }}
|
||||
**Branch/Ref:** ${{ github.ref }}
|
||||
**Triggered by:** ${{ github.actor }}
|
||||
|
||||
### Clang-tidy Output:
|
||||
\`\`\`
|
||||
EOF
|
||||
|
||||
# Append clang-tidy output (filter for errors and warnings)
|
||||
if [ -f clang-tidy-output.txt ]; then
|
||||
# Extract lines containing 'error:', 'warning:', or 'note:'
|
||||
grep -E '(error:|warning:|note:)' clang-tidy-output.txt > filtered-output.txt || true
|
||||
|
||||
# If filtered output is empty, use original (might be a different error format)
|
||||
if [ ! -s filtered-output.txt ]; then
|
||||
cp clang-tidy-output.txt filtered-output.txt
|
||||
fi
|
||||
|
||||
# Truncate if too large
|
||||
head -c 60000 filtered-output.txt >> issue.md
|
||||
if [ "$(wc -c < filtered-output.txt)" -gt 60000 ]; then
|
||||
echo "" >> issue.md
|
||||
echo "... (output truncated, see artifacts for full output)" >> issue.md
|
||||
fi
|
||||
|
||||
rm filtered-output.txt
|
||||
else
|
||||
echo "No output file found" >> issue.md
|
||||
fi
|
||||
|
||||
cat >> issue.md <<EOF
|
||||
\`\`\`
|
||||
|
||||
**Workflow run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
---
|
||||
*This issue was automatically created by the clang-tidy workflow.*
|
||||
EOF
|
||||
|
||||
# Create the issue
|
||||
gh issue create \
|
||||
--label "Bug,Clang-tidy" \
|
||||
--title "Clang-tidy check failed" \
|
||||
--body-file ./issue.md \
|
||||
> create_issue.log
|
||||
|
||||
created_issue="$(sed 's|.*/||' create_issue.log)"
|
||||
echo "created_issue=$created_issue" >> $GITHUB_OUTPUT
|
||||
echo "Created issue #$created_issue"
|
||||
|
||||
rm -f create_issue.log issue.md clang-tidy-output.txt
|
||||
|
||||
- name: Fail the workflow if clang-tidy failed
|
||||
if: steps.run_clang_tidy.outcome != 'success'
|
||||
run: |
|
||||
echo "Clang-tidy check failed!"
|
||||
exit 1
|
||||
212
.github/workflows/reusable-clang-tidy.yml
vendored
212
.github/workflows/reusable-clang-tidy.yml
vendored
@@ -1,12 +1,8 @@
|
||||
name: Clang-tidy check
|
||||
name: Run clang-tidy on files
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
check_only_changed:
|
||||
description: "Check only changed files in PR. If false, checks all files in the repository."
|
||||
type: boolean
|
||||
default: false
|
||||
create_issue_on_failure:
|
||||
description: "Whether to create an issue if the check failed"
|
||||
type: boolean
|
||||
@@ -16,40 +12,182 @@ defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
BUILD_DIR: build
|
||||
BUILD_TYPE: Debug # Debug so that ASSERTS and such participate in clang-tidy check
|
||||
|
||||
OUTPUT_FILE: /tmp/clang-tidy-output.txt
|
||||
FILTERED_OUTPUT_FILE: /tmp/clang-tidy-filtered-output.txt
|
||||
DIFF_FILE: /tmp/clang-tidy-git-diff.txt
|
||||
ISSUE_FILE: /tmp/clang-tidy-issue.md
|
||||
|
||||
COMPILER: clang
|
||||
|
||||
jobs:
|
||||
determine-files:
|
||||
name: Determine files to check
|
||||
if: ${{ inputs.check_only_changed }}
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
clang_tidy_config_changed: ${{ steps.changed_clang_tidy.outputs.any_changed }}
|
||||
any_cpp_changed: ${{ steps.changed_files.outputs.any_changed }}
|
||||
all_changed_files: ${{ steps.changed_files.outputs.all_changed_files }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Get changed C++ files
|
||||
id: changed_files
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
with:
|
||||
files: |
|
||||
**/*.cpp
|
||||
**/*.h
|
||||
**/*.ipp
|
||||
separator: " "
|
||||
|
||||
- name: Get changed clang-tidy configuration
|
||||
id: changed_clang_tidy
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
with:
|
||||
files: |
|
||||
.clang-tidy
|
||||
permissions:
|
||||
contents: read
|
||||
uses: XRPLF/actions/.github/workflows/determine-tidy-files.yml@d041ac9f1fa9f07a4ba335eb4c1c82233fb3fef6
|
||||
|
||||
run-clang-tidy:
|
||||
name: Run clang tidy
|
||||
needs: [determine-files]
|
||||
if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.any_cpp_changed == 'true' || needs.determine-files.outputs.clang_tidy_config_changed == 'true') }}
|
||||
uses: ./.github/workflows/reusable-clang-tidy-files.yml
|
||||
with:
|
||||
files: ${{ (needs.determine-files.outputs.clang_tidy_config_changed != 'true' && inputs.check_only_changed) && needs.determine-files.outputs.all_changed_files || '' }}
|
||||
create_issue_on_failure: ${{ inputs.create_issue_on_failure }}
|
||||
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"
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
- name: Print build environment
|
||||
uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574
|
||||
|
||||
- name: Get number of processors
|
||||
uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
id: nproc
|
||||
|
||||
- name: Set compiler environment
|
||||
uses: ./.github/actions/set-compiler-env
|
||||
with:
|
||||
compiler: ${{ env.COMPILER }}
|
||||
|
||||
- name: Setup Conan
|
||||
uses: ./.github/actions/setup-conan
|
||||
|
||||
- name: Build dependencies
|
||||
uses: ./.github/actions/build-deps
|
||||
with:
|
||||
build_nproc: ${{ steps.nproc.outputs.nproc }}
|
||||
build_type: ${{ env.BUILD_TYPE }}
|
||||
log_verbosity: verbose
|
||||
|
||||
- name: Configure CMake
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
run: |
|
||||
cmake \
|
||||
-G 'Ninja' \
|
||||
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \
|
||||
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
|
||||
-Dtests=ON \
|
||||
-Dwerr=ON \
|
||||
-Dxrpld=ON \
|
||||
..
|
||||
|
||||
# clang-tidy needs headers generated from proto files
|
||||
- name: Build libxrpl.libpb
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
run: |
|
||||
ninja -j ${{ steps.nproc.outputs.nproc }} xrpl.libpb
|
||||
|
||||
- name: Run clang tidy
|
||||
id: run_clang_tidy
|
||||
continue-on-error: true
|
||||
env:
|
||||
TARGETS: ${{ needs.determine-files.outputs.need_full_run != 'true' && needs.determine-files.outputs.cpp_changed_files || '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}"
|
||||
|
||||
- name: Print errors
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
|
||||
run: |
|
||||
sed '/error\||/!d' "${OUTPUT_FILE}"
|
||||
|
||||
- name: Upload clang-tidy output
|
||||
if: ${{ github.event.repository.visibility == 'public' && steps.run_clang_tidy.outcome != 'success' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
path: ${{ env.OUTPUT_FILE }}
|
||||
archive: false
|
||||
retention-days: 30
|
||||
|
||||
- name: Check for changes
|
||||
id: files_changed
|
||||
continue-on-error: true
|
||||
run: |
|
||||
git diff --exit-code
|
||||
|
||||
- name: Fix style
|
||||
if: ${{ steps.files_changed.outcome != 'success' }}
|
||||
run: |
|
||||
pre-commit run --all-files || true
|
||||
|
||||
- name: Generate git diff
|
||||
if: ${{ steps.files_changed.outcome != 'success' }}
|
||||
run: |
|
||||
git diff | tee "${DIFF_FILE}"
|
||||
|
||||
- name: Upload clang-tidy diff output
|
||||
if: ${{ github.event.repository.visibility == 'public' && steps.files_changed.outcome != 'success' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
path: ${{ env.DIFF_FILE }}
|
||||
archive: false
|
||||
retention-days: 30
|
||||
|
||||
- name: Write issue header
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
|
||||
run: |
|
||||
cat >"${ISSUE_FILE}" <<EOF
|
||||
## Clang-tidy Check Failed
|
||||
|
||||
### Clang-tidy Output:
|
||||
\`\`\`
|
||||
EOF
|
||||
|
||||
- name: Append clang-tidy output to issue body (filter for errors and warnings)
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
|
||||
run: |
|
||||
if [ -f "${OUTPUT_FILE}" ]; then
|
||||
# Extract lines containing 'error:', 'warning:', or 'note:'
|
||||
grep -E '(error:|warning:|note:)' "${OUTPUT_FILE}" >"${FILTERED_OUTPUT_FILE}" || true
|
||||
|
||||
# If filtered output is empty, use original (might be a different error format)
|
||||
if [ ! -s "${FILTERED_OUTPUT_FILE}" ]; then
|
||||
cp "${OUTPUT_FILE}" "${FILTERED_OUTPUT_FILE}"
|
||||
fi
|
||||
|
||||
# Truncate if too large
|
||||
head -c 60000 "${FILTERED_OUTPUT_FILE}" >>"${ISSUE_FILE}"
|
||||
if [ "$(wc -c <"${FILTERED_OUTPUT_FILE}")" -gt 60000 ]; then
|
||||
echo "" >>"${ISSUE_FILE}"
|
||||
echo "... (output truncated, see artifacts for full output)" >>"${ISSUE_FILE}"
|
||||
fi
|
||||
|
||||
rm "${FILTERED_OUTPUT_FILE}"
|
||||
else
|
||||
echo "No output file found" >>"${ISSUE_FILE}"
|
||||
fi
|
||||
|
||||
- name: Append issue footer
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
|
||||
run: |
|
||||
cat >>"${ISSUE_FILE}" <<EOF
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
*This issue was automatically created by the clang-tidy workflow.*
|
||||
EOF
|
||||
|
||||
- name: Create issue
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' && inputs.create_issue_on_failure }}
|
||||
uses: XRPLF/actions/create-issue@2b8bc36af85b88bca0dd7bfac2e2dc05f94ad712
|
||||
with:
|
||||
title: "Clang-tidy check failed"
|
||||
body_file: ${{ env.ISSUE_FILE }}
|
||||
labels: "Bug,Clang-tidy"
|
||||
assignees: "godexsoft,mathbunnyru"
|
||||
|
||||
- name: Fail if clang-tidy found issues
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
|
||||
run: |
|
||||
echo "Clang-tidy check failed!"
|
||||
exit 1
|
||||
|
||||
81
.github/workflows/reusable-package.yml
vendored
Normal file
81
.github/workflows/reusable-package.yml
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
# 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.
|
||||
name: Package
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
pkg_release:
|
||||
description: "Package release number. Increment when repackaging the same executable."
|
||||
required: false
|
||||
type: string
|
||||
default: "1"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
BUILD_DIR: build
|
||||
|
||||
jobs:
|
||||
generate-matrix:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.generate.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
|
||||
- name: Generate packaging matrix
|
||||
id: generate
|
||||
working-directory: .github/scripts/strategy-matrix
|
||||
run: ./generate.py --packaging >>"${GITHUB_OUTPUT}"
|
||||
|
||||
package:
|
||||
needs: [generate-matrix]
|
||||
if: ${{ github.event.repository.visibility == 'public' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
|
||||
name: "${{ matrix.artifact_name }}"
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: ${{ matrix.image }}
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Download pre-built binary
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
path: ${{ env.BUILD_DIR }}
|
||||
|
||||
- name: Make binary executable
|
||||
run: chmod +x "${BUILD_DIR}/xrpld"
|
||||
|
||||
- name: Build 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
|
||||
18
.github/workflows/reusable-strategy-matrix.yml
vendored
18
.github/workflows/reusable-strategy-matrix.yml
vendored
@@ -4,15 +4,9 @@ on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
os:
|
||||
description: 'The operating system to use for the build ("linux", "macos", "windows").'
|
||||
description: 'The operating system to use for the build ("linux", "macos", "windows", or empty for all).'
|
||||
required: false
|
||||
type: string
|
||||
strategy_matrix:
|
||||
# TODO: Support additional strategies, e.g. "ubuntu" for generating all Ubuntu configurations.
|
||||
description: 'The strategy matrix to use for generating the configurations ("minimal", "all").'
|
||||
required: false
|
||||
type: string
|
||||
default: "minimal"
|
||||
outputs:
|
||||
matrix:
|
||||
description: "The generated strategy matrix."
|
||||
@@ -29,17 +23,17 @@ jobs:
|
||||
matrix: ${{ steps.generate.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: 3.13
|
||||
python-version: "3.13"
|
||||
|
||||
- name: Generate strategy matrix
|
||||
working-directory: .github/scripts/strategy-matrix
|
||||
id: generate
|
||||
env:
|
||||
GENERATE_CONFIG: ${{ inputs.os != '' && format('--config={0}.json', inputs.os) || '' }}
|
||||
GENERATE_OPTION: ${{ inputs.strategy_matrix == 'all' && '--all' || '' }}
|
||||
run: ./generate.py ${GENERATE_OPTION} ${GENERATE_CONFIG} >> "${GITHUB_OUTPUT}"
|
||||
GENERATE_CONFIG: ${{ inputs.os != '' && format('--config={0}', inputs.os) || '' }}
|
||||
GENERATE_EVENT: ${{ github.event_name }}
|
||||
run: ./generate.py ${GENERATE_CONFIG} --event="${GENERATE_EVENT}" >>"${GITHUB_OUTPUT}"
|
||||
|
||||
24
.github/workflows/reusable-upload-recipe.yml
vendored
24
.github/workflows/reusable-upload-recipe.yml
vendored
@@ -14,7 +14,7 @@ on:
|
||||
description: "The URL of the Conan endpoint to use."
|
||||
required: false
|
||||
type: string
|
||||
default: https://conan.ripplex.io
|
||||
default: https://conan.xrplf.org/repository/conan/
|
||||
|
||||
secrets:
|
||||
remote_username:
|
||||
@@ -40,10 +40,14 @@ defaults:
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/ci/ubuntu-noble:gcc-13-sha-5dd7158
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-e29b523
|
||||
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Generate build version number
|
||||
id: version
|
||||
@@ -56,15 +60,9 @@ jobs:
|
||||
remote_url: ${{ inputs.remote_url }}
|
||||
|
||||
- name: Log into Conan remote
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
REMOTE_USERNAME: ${{ secrets.remote_username }}
|
||||
REMOTE_PASSWORD: ${{ secrets.remote_password }}
|
||||
run: conan remote login "${REMOTE_NAME}" "${REMOTE_USERNAME}" --password "${REMOTE_PASSWORD}"
|
||||
run: conan remote login "${REMOTE_NAME}" "${CONAN_LOGIN_USERNAME_XRPLF}" --password "${CONAN_PASSWORD_XRPLF}"
|
||||
|
||||
- name: Upload Conan recipe (version)
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
run: |
|
||||
conan export . --version=${{ steps.version.outputs.version }}
|
||||
conan upload --confirm --check --remote="${REMOTE_NAME}" xrpl/${{ steps.version.outputs.version }}
|
||||
@@ -73,8 +71,6 @@ jobs:
|
||||
# 'develop' branch, see on-trigger.yml.
|
||||
- name: Upload Conan recipe (develop)
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
run: |
|
||||
conan export . --version=develop
|
||||
conan upload --confirm --check --remote="${REMOTE_NAME}" xrpl/develop
|
||||
@@ -83,8 +79,6 @@ jobs:
|
||||
# one of the 'release' branches, see on-pr.yml.
|
||||
- name: Upload Conan recipe (rc)
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
run: |
|
||||
conan export . --version=rc
|
||||
conan upload --confirm --check --remote="${REMOTE_NAME}" xrpl/rc
|
||||
@@ -93,8 +87,6 @@ jobs:
|
||||
# release, see on-tag.yml.
|
||||
- name: Upload Conan recipe (release)
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
run: |
|
||||
conan export . --version=release
|
||||
conan upload --confirm --check --remote="${REMOTE_NAME}" xrpl/release
|
||||
|
||||
310
.github/workflows/telemetry-validation.yml
vendored
Normal file
310
.github/workflows/telemetry-validation.yml
vendored
Normal file
@@ -0,0 +1,310 @@
|
||||
# Telemetry Validation CI Workflow
|
||||
#
|
||||
# Builds rippled with telemetry enabled, runs the multi-node workload
|
||||
# harness, validates all telemetry data, and runs performance benchmarks.
|
||||
#
|
||||
# This is a separate workflow from the main CI. It runs:
|
||||
# - On manual dispatch (workflow_dispatch)
|
||||
# - On pushes to telemetry-related branches
|
||||
#
|
||||
# The workflow is intentionally heavyweight (builds rippled, starts Docker
|
||||
# services, runs a multi-node cluster) — it validates the full telemetry
|
||||
# stack end-to-end rather than individual unit tests.
|
||||
#
|
||||
# Architecture: two jobs to leverage cached dependencies:
|
||||
# 1. build-xrpld — runs on a self-hosted runner inside the same container
|
||||
# image the main CI uses (debian-bookworm-gcc-13). This ensures Conan
|
||||
# packages are fetched from the XRPLF remote instead of built from
|
||||
# source, and ccache hits the remote cache.
|
||||
# 2. validate-telemetry — runs on ubuntu-latest (which has Docker) to
|
||||
# launch the telemetry stack (OTel collector, Prometheus, Tempo, etc.)
|
||||
# and validate the full pipeline end-to-end.
|
||||
|
||||
name: Telemetry Validation
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
rpc_rate:
|
||||
description: "RPC load rate (requests per second)"
|
||||
required: false
|
||||
default: "50"
|
||||
rpc_duration:
|
||||
description: "RPC load duration (seconds)"
|
||||
required: false
|
||||
default: "120"
|
||||
tx_tps:
|
||||
description: "Transaction submit rate (TPS)"
|
||||
required: false
|
||||
default: "5"
|
||||
tx_duration:
|
||||
description: "Transaction submit duration (seconds)"
|
||||
required: false
|
||||
default: "120"
|
||||
run_benchmark:
|
||||
description: "Run performance benchmarks"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
push:
|
||||
branches:
|
||||
- "pratik/otel-phase*"
|
||||
- "feature/otel-*"
|
||||
- "feature/telemetry-*"
|
||||
paths:
|
||||
- ".github/workflows/telemetry-validation.yml"
|
||||
- "docker/telemetry/**"
|
||||
- "include/xrpl/basics/Telemetry*.h"
|
||||
- "src/xrpld/app/misc/Telemetry*"
|
||||
|
||||
concurrency:
|
||||
group: telemetry-validation-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
BUILD_DIR: build
|
||||
|
||||
jobs:
|
||||
# ── Job 1: Build xrpld in the same container the main CI uses ──────
|
||||
# This ensures Conan binary packages are fetched from the XRPLF remote
|
||||
# (matching package IDs) and ccache hits the remote compilation cache.
|
||||
build-xrpld:
|
||||
name: Build xrpld
|
||||
runs-on: [self-hosted, Linux, X64, heavy]
|
||||
container: ghcr.io/xrplf/ci/debian-bookworm:gcc-13-sha-ab4d1f0
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CCACHE_NAMESPACE: telemetry-validation
|
||||
CCACHE_REMOTE_ONLY: true
|
||||
CCACHE_REMOTE_STORAGE: http://cache.dev.ripplex.io:8080|layout=bazel
|
||||
CCACHE_SLOPPINESS: include_file_ctime,include_file_mtime
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
with:
|
||||
enable_ccache: ${{ github.repository_owner == 'XRPLF' }}
|
||||
|
||||
- name: Print build environment
|
||||
uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574
|
||||
|
||||
- name: Get number of processors
|
||||
uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
id: nproc
|
||||
with:
|
||||
subtract: 2
|
||||
|
||||
- name: Setup Conan
|
||||
uses: ./.github/actions/setup-conan
|
||||
|
||||
- name: Build dependencies
|
||||
uses: ./.github/actions/build-deps
|
||||
with:
|
||||
build_nproc: ${{ steps.nproc.outputs.nproc }}
|
||||
build_type: Release
|
||||
log_verbosity: verbose
|
||||
|
||||
- name: Configure CMake
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
run: |
|
||||
cmake \
|
||||
-G Ninja \
|
||||
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
..
|
||||
|
||||
- name: Build xrpld
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
env:
|
||||
BUILD_NPROC: ${{ steps.nproc.outputs.nproc }}
|
||||
run: |
|
||||
cmake \
|
||||
--build . \
|
||||
--config Release \
|
||||
--parallel "${BUILD_NPROC}" \
|
||||
--target xrpld
|
||||
|
||||
- name: Show ccache statistics
|
||||
if: ${{ github.repository_owner == 'XRPLF' }}
|
||||
run: ccache --show-stats -vv
|
||||
|
||||
- name: Upload xrpld binary
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: xrpld-telemetry
|
||||
path: ${{ env.BUILD_DIR }}/xrpld
|
||||
retention-days: 1
|
||||
if-no-files-found: error
|
||||
|
||||
# ── Job 2: Run telemetry validation on ubuntu-latest (has Docker) ──
|
||||
validate-telemetry:
|
||||
name: Telemetry Stack Validation
|
||||
needs: build-xrpld
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: pip3 install -r docker/telemetry/workload/requirements.txt
|
||||
|
||||
- name: Download xrpld binary
|
||||
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
|
||||
with:
|
||||
name: xrpld-telemetry
|
||||
path: ${{ env.BUILD_DIR }}
|
||||
|
||||
- name: Make binaries and scripts executable
|
||||
run: |
|
||||
chmod +x ${{ env.BUILD_DIR }}/xrpld
|
||||
chmod +x docker/telemetry/workload/*.sh
|
||||
|
||||
- name: Run full telemetry validation
|
||||
id: validation
|
||||
env:
|
||||
RPC_RATE: ${{ github.event.inputs.rpc_rate || '50' }}
|
||||
RPC_DURATION: ${{ github.event.inputs.rpc_duration || '120' }}
|
||||
TX_TPS: ${{ github.event.inputs.tx_tps || '5' }}
|
||||
TX_DURATION: ${{ github.event.inputs.tx_duration || '120' }}
|
||||
RUN_BENCHMARK: ${{ github.event.inputs.run_benchmark }}
|
||||
run: |
|
||||
ARGS="--xrpld ${{ env.BUILD_DIR }}/xrpld --skip-loki"
|
||||
ARGS="$ARGS --rpc-rate $RPC_RATE"
|
||||
ARGS="$ARGS --rpc-duration $RPC_DURATION"
|
||||
ARGS="$ARGS --tx-tps $TX_TPS"
|
||||
ARGS="$ARGS --tx-duration $TX_DURATION"
|
||||
if [ "$RUN_BENCHMARK" = "true" ]; then
|
||||
ARGS="$ARGS --with-benchmark"
|
||||
fi
|
||||
docker/telemetry/workload/run-full-validation.sh $ARGS
|
||||
# continue-on-error allows subsequent steps (artifact upload,
|
||||
# summary printing) to run even if validation fails. The final
|
||||
# "Check validation result" step re-checks steps.validation.outcome
|
||||
# (the pre-continue-on-error result) and fails the job properly.
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload validation reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: telemetry-validation-reports
|
||||
path: /tmp/xrpld-validation/reports/
|
||||
retention-days: 30
|
||||
|
||||
- name: Upload node logs
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: xrpld-node-logs
|
||||
path: /tmp/xrpld-validation/node*/debug.log
|
||||
retention-days: 7
|
||||
|
||||
- name: Print validation summary
|
||||
if: always()
|
||||
run: |
|
||||
REPORT="/tmp/xrpld-validation/reports/validation-report.json"
|
||||
if [ -f "$REPORT" ]; then
|
||||
echo "## Telemetry Validation Results" >>"$GITHUB_STEP_SUMMARY"
|
||||
echo "" >>"$GITHUB_STEP_SUMMARY"
|
||||
TOTAL=$(jq '.summary.total' "$REPORT")
|
||||
PASSED=$(jq '.summary.passed' "$REPORT")
|
||||
FAILED=$(jq '.summary.failed' "$REPORT")
|
||||
echo "| Metric | Value |" >>"$GITHUB_STEP_SUMMARY"
|
||||
echo "|--------|-------|" >>"$GITHUB_STEP_SUMMARY"
|
||||
echo "| Total Checks | $TOTAL |" >>"$GITHUB_STEP_SUMMARY"
|
||||
echo "| Passed | $PASSED |" >>"$GITHUB_STEP_SUMMARY"
|
||||
echo "| Failed | $FAILED |" >>"$GITHUB_STEP_SUMMARY"
|
||||
echo "" >>"$GITHUB_STEP_SUMMARY"
|
||||
if [ "$FAILED" -gt 0 ]; then
|
||||
echo "### Failed Checks" >>"$GITHUB_STEP_SUMMARY"
|
||||
echo "" >>"$GITHUB_STEP_SUMMARY"
|
||||
jq -r '.checks[] | select(.passed == false) | "- **\(.name)**: \(.message)"' "$REPORT" >>"$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Publishes captured OTel timings + regression report to the Step Summary.
|
||||
# When the committed baseline is a placeholder, emits a fenced JSON block
|
||||
# that can be copy-pasted directly into baselines/baseline-timings.json.
|
||||
# When the baseline is populated, summarises the top regressions so the
|
||||
# PR author sees the failure reason without downloading artifacts.
|
||||
- name: Print regression summary
|
||||
if: always()
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TIMINGS="/tmp/xrpld-validation/reports/timings.json"
|
||||
REGRESSION="/tmp/xrpld-validation/reports/regression-report.json"
|
||||
BASELINE="docker/telemetry/workload/baselines/baseline-timings.json"
|
||||
|
||||
if [ ! -f "$TIMINGS" ]; then
|
||||
echo "## Regression Gate: no timings captured" >>"$GITHUB_STEP_SUMMARY"
|
||||
echo "::warning::capture_timings.py did not produce timings.json — regression gate was not evaluated."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ ! -f "$BASELINE" ]; then
|
||||
echo "## Regression Gate: baseline file missing" >>"$GITHUB_STEP_SUMMARY"
|
||||
echo "::error::baselines/baseline-timings.json not found in checkout"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# NOTE: do NOT use `jq -e` here. With -e, jq exits non-zero when the
|
||||
# filter's result is boolean false — which is the normal case for a
|
||||
# populated (non-placeholder) baseline — and that would be
|
||||
# misreported as a parse failure. Plain `jq -r` exits 0 on any valid
|
||||
# JSON, so a real non-zero exit genuinely means malformed JSON.
|
||||
IS_PLACEHOLDER=$(jq -r '.placeholder == true or (.metrics | length == 0)' "$BASELINE") || {
|
||||
echo "::error::Failed to parse baseline JSON"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "## OTel Timings Regression Gate" >>"$GITHUB_STEP_SUMMARY"
|
||||
echo "" >>"$GITHUB_STEP_SUMMARY"
|
||||
|
||||
if [ "$IS_PLACEHOLDER" = "true" ]; then
|
||||
echo "### Paste into \`baselines/baseline-timings.json\`" >>"$GITHUB_STEP_SUMMARY"
|
||||
echo "" >>"$GITHUB_STEP_SUMMARY"
|
||||
echo "The committed baseline is a placeholder. Open a PR replacing" \
|
||||
"its contents with the JSON block below to activate the" \
|
||||
"regression gate." >>"$GITHUB_STEP_SUMMARY"
|
||||
echo "" >>"$GITHUB_STEP_SUMMARY"
|
||||
echo '```json' >>"$GITHUB_STEP_SUMMARY"
|
||||
cat "$TIMINGS" >>"$GITHUB_STEP_SUMMARY"
|
||||
echo '```' >>"$GITHUB_STEP_SUMMARY"
|
||||
elif [ -f "$REGRESSION" ]; then
|
||||
REGR_COUNT=$(jq -e '.summary.regressions' "$REGRESSION") || REGR_COUNT=0
|
||||
IMPR_COUNT=$(jq -e '.summary.improvements' "$REGRESSION") || IMPR_COUNT=0
|
||||
TOTAL=$(jq -e '.summary.total' "$REGRESSION") || TOTAL=0
|
||||
echo "| Stat | Count |" >>"$GITHUB_STEP_SUMMARY"
|
||||
echo "|------|-------|" >>"$GITHUB_STEP_SUMMARY"
|
||||
echo "| Metrics compared | $TOTAL |" >>"$GITHUB_STEP_SUMMARY"
|
||||
echo "| Regressions | $REGR_COUNT |" >>"$GITHUB_STEP_SUMMARY"
|
||||
echo "| Improvements | $IMPR_COUNT |" >>"$GITHUB_STEP_SUMMARY"
|
||||
echo "" >>"$GITHUB_STEP_SUMMARY"
|
||||
if [ "$REGR_COUNT" -gt 0 ]; then
|
||||
echo "### Regressions" >>"$GITHUB_STEP_SUMMARY"
|
||||
echo "" >>"$GITHUB_STEP_SUMMARY"
|
||||
echo "| Metric | Baseline | Current | Δ | % | Unit |" >>"$GITHUB_STEP_SUMMARY"
|
||||
echo "|--------|---------:|--------:|--:|--:|------|" >>"$GITHUB_STEP_SUMMARY"
|
||||
jq -r '.metrics[] | select(.regressed) | "| \(.key) | \(.baseline) | \(.current) | \(.delta) | \(.pct_change)% | \(.unit) |"' \
|
||||
"$REGRESSION" >>"$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
run: |
|
||||
docker/telemetry/workload/run-full-validation.sh --cleanup 2>/dev/null || true
|
||||
|
||||
- name: Check validation result
|
||||
if: steps.validation.outcome == 'failure'
|
||||
run: |
|
||||
echo "Telemetry validation failed. Check the uploaded reports for details."
|
||||
exit 1
|
||||
24
.github/workflows/upload-conan-deps.yml
vendored
24
.github/workflows/upload-conan-deps.yml
vendored
@@ -30,10 +30,11 @@ on:
|
||||
- ".github/scripts/strategy-matrix/**"
|
||||
- conanfile.py
|
||||
- conan.lock
|
||||
- conan/profiles/**
|
||||
|
||||
env:
|
||||
CONAN_REMOTE_NAME: xrplf
|
||||
CONAN_REMOTE_URL: https://conan.ripplex.io
|
||||
CONAN_REMOTE_URL: https://conan.xrplf.org/repository/conan/
|
||||
NPROC_SUBTRACT: 2
|
||||
|
||||
concurrency:
|
||||
@@ -48,8 +49,6 @@ jobs:
|
||||
# Generate the strategy matrix to be used by the following job.
|
||||
generate-matrix:
|
||||
uses: ./.github/workflows/reusable-strategy-matrix.yml
|
||||
with:
|
||||
strategy_matrix: ${{ github.event_name == 'pull_request' && 'minimal' || 'all' }}
|
||||
|
||||
# Build and upload the dependencies for each configuration.
|
||||
run-upload-conan-deps:
|
||||
@@ -58,24 +57,23 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
|
||||
max-parallel: 10
|
||||
runs-on: ${{ matrix.architecture.runner }}
|
||||
container: ${{ contains(matrix.architecture.platform, 'linux') && format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}-sha-{4}', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version, matrix.os.image_sha) || null }}
|
||||
container: ${{ matrix.image || null }}
|
||||
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@2bbc2dc1abeec7bfaa886804ab86871ac201764e
|
||||
uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
- name: Print build environment
|
||||
uses: ./.github/actions/print-env
|
||||
uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574
|
||||
|
||||
- name: Get number of processors
|
||||
uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
@@ -83,6 +81,12 @@ jobs:
|
||||
with:
|
||||
subtract: ${{ env.NPROC_SUBTRACT }}
|
||||
|
||||
- name: Set compiler environment (Linux)
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
uses: ./.github/actions/set-compiler-env
|
||||
with:
|
||||
compiler: ${{ matrix.compiler }}
|
||||
|
||||
- name: Setup Conan
|
||||
env:
|
||||
SANITIZERS: ${{ matrix.sanitizers }}
|
||||
@@ -104,10 +108,12 @@ jobs:
|
||||
|
||||
- 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.CONAN_REMOTE_USERNAME }}" --password "${{ secrets.CONAN_REMOTE_PASSWORD }}"
|
||||
run: conan remote login "${CONAN_REMOTE_NAME}" "${{ secrets.NEXUS_REMOTE_USERNAME }}" --password "${{ secrets.NEXUS_REMOTE_PASSWORD }}"
|
||||
|
||||
- name: Upload Conan packages
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }}
|
||||
env:
|
||||
FORCE_OPTION: ${{ github.event.inputs.force_upload == 'true' && '--force' || '' }}
|
||||
CONAN_LOGIN_USERNAME_XRPLF: ${{ secrets.NEXUS_REMOTE_USERNAME }}
|
||||
CONAN_PASSWORD_XRPLF: ${{ secrets.NEXUS_REMOTE_PASSWORD }}
|
||||
run: conan upload "*" --remote="${CONAN_REMOTE_NAME}" --confirm ${FORCE_OPTION}
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -13,6 +13,7 @@
|
||||
Debug/
|
||||
Release/
|
||||
/.build/
|
||||
/.venv/
|
||||
/build/
|
||||
/db/
|
||||
/out.txt
|
||||
@@ -85,3 +86,8 @@ __pycache__
|
||||
|
||||
# clangd cache
|
||||
/.cache
|
||||
docker/telemetry/workload/__pycache__/
|
||||
.claude/
|
||||
|
||||
# Env. file carrying environmental setup data for local or cloud runs.
|
||||
.env.*
|
||||
|
||||
@@ -15,40 +15,85 @@ repos:
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
args: [--maxkb=400, --enforce-all]
|
||||
- id: check-executables-have-shebangs
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: mixed-line-ending
|
||||
- id: check-merge-conflict
|
||||
args: [--assume-in-merge]
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: clang-tidy
|
||||
name: "clang-tidy (enable with: TIDY=1)"
|
||||
entry: ./bin/pre-commit/clang_tidy_check.py
|
||||
language: python
|
||||
types_or: [c++, c]
|
||||
exclude: ^include/xrpl/protocol_autogen
|
||||
pass_filenames: false # script determines the staged files itself
|
||||
- id: fix-include-style
|
||||
name: fix include style
|
||||
entry: ./bin/pre-commit/fix_include_style.py
|
||||
language: python
|
||||
types_or: [c++, c]
|
||||
exclude: ^include/xrpl/protocol_autogen/(transactions|ledger_entries)/
|
||||
- id: fix-pragma-once
|
||||
name: fix missing '#pragma once' declarations in header files
|
||||
language: python
|
||||
entry: ./bin/pre-commit/fix_pragma_once.py
|
||||
files: \.(h|hpp)$
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: cd481d7b0bfb5c7b3090c21846317f9a8262e891 # frozen: v22.1.0
|
||||
rev: dd18dad857d6133e90bbe478f4f2f22ec0030269 # frozen: v22.1.5
|
||||
hooks:
|
||||
- id: clang-format
|
||||
args: [--style=file]
|
||||
"types_or": [c++, c, proto]
|
||||
types_or: [c++, c, proto]
|
||||
exclude: ^include/xrpl/protocol_autogen/(transactions|ledger_entries)/
|
||||
|
||||
- repo: https://github.com/BlankSpruce/gersemi
|
||||
rev: 0.26.0
|
||||
- repo: https://github.com/BlankSpruce/gersemi-pre-commit
|
||||
rev: faadd6a9d852369ca94f4d15b2404c967ba8cb01 # frozen: 0.27.6
|
||||
hooks:
|
||||
- id: gersemi
|
||||
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: c2bc67fe8f8f549cc489e00ba8b45aa18ee713b1 # frozen: v3.8.1
|
||||
rev: 515f543f5718ebfd6ce22e16708bb32c68ff96e1 # frozen: v3.8.3
|
||||
hooks:
|
||||
- id: prettier
|
||||
args: [--end-of-line=auto]
|
||||
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: ea488cebbfd88a5f50b8bd95d5c829d0bb76feb8 # frozen: 26.1.0
|
||||
rev: 4160603246a6b365d4a2af661c6d71b0a0f50478 # frozen: 26.5.1
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
- repo: https://github.com/scop/pre-commit-shfmt
|
||||
rev: 05c1426671b9237fb5e1444dd63aa5731bec0dfb # frozen: v3.13.1-1
|
||||
hooks:
|
||||
- id: shfmt
|
||||
args: [--write, --indent=4, --case-indent=true]
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: format-inline-bash-workflows
|
||||
name: "format `run:` blocks in workflows/actions"
|
||||
entry: ./.github/scripts/format-inline-bash.py
|
||||
language: python
|
||||
files: ^\.github/(workflows|actions)/.*\.ya?ml$
|
||||
- id: format-inline-bash-markdown
|
||||
name: "format ```bash blocks in markdown"
|
||||
entry: ./.github/scripts/format-inline-bash.py
|
||||
language: python
|
||||
files: \.md$
|
||||
|
||||
- repo: https://github.com/streetsidesoftware/cspell-cli
|
||||
rev: a42085ade523f591dca134379a595e7859986445 # frozen: v9.7.0
|
||||
rev: 4643f154907327ee0a2c7038f0296e0dd77d9776 # frozen: v10.0.0
|
||||
hooks:
|
||||
- id: cspell # Spell check changed files
|
||||
exclude: (.config/cspell.config.yaml|^include/xrpl/protocol_autogen/(transactions|ledger_entries)/)
|
||||
exclude: |
|
||||
(?x)^(
|
||||
.config/cspell.config.yaml|
|
||||
include/xrpl/protocol_autogen/(transactions|ledger_entries)/.*
|
||||
)$
|
||||
- id: cspell # Spell check the commit message
|
||||
name: check commit message spelling
|
||||
args:
|
||||
|
||||
@@ -4,23 +4,23 @@ This changelog is intended to list all updates to the [public API methods](https
|
||||
|
||||
For info about how [API versioning](https://xrpl.org/request-formatting.html#api-versioning) works, including examples, please view the [XLS-22d spec](https://github.com/XRPLF/XRPL-Standards/discussions/54). For details about the implementation of API versioning, view the [implementation PR](https://github.com/XRPLF/rippled/pull/3155). API versioning ensures existing integrations and users continue to receive existing behavior, while those that request a higher API version will experience new behavior.
|
||||
|
||||
The API version controls the API behavior you see. This includes what properties you see in responses, what parameters you're permitted to send in requests, and so on. You specify the API version in each of your requests. When a breaking change is introduced to the `rippled` API, a new version is released. To avoid breaking your code, you should set (or increase) your version when you're ready to upgrade.
|
||||
The API version controls the API behavior you see. This includes what properties you see in responses, what parameters you're permitted to send in requests, and so on. You specify the API version in each of your requests. When a breaking change is introduced to the `xrpld` API, a new version is released. To avoid breaking your code, you should set (or increase) your version when you're ready to upgrade.
|
||||
|
||||
The [commandline](https://xrpl.org/docs/references/http-websocket-apis/api-conventions/request-formatting/#commandline-format) always uses the latest API version. The command line is intended for ad-hoc usage by humans, not programs or automated scripts. The command line is not meant for use in production code.
|
||||
|
||||
For a log of breaking changes, see the **API Version [number]** headings. In general, breaking changes are associated with a particular API Version number. For non-breaking changes, scroll to the **XRP Ledger version [x.y.z]** headings. Non-breaking changes are associated with a particular XRP Ledger (`rippled`) release.
|
||||
For a log of breaking changes, see the **API Version [number]** headings. In general, breaking changes are associated with a particular API Version number. For non-breaking changes, scroll to the **XRP Ledger version [x.y.z]** headings. Non-breaking changes are associated with a particular XRP Ledger (`xrpld`) release.
|
||||
|
||||
## API Version 3 (Beta)
|
||||
|
||||
API version 3 is currently a beta API. It requires enabling `[beta_rpc_api]` in the rippled configuration to use. See [API-VERSION-3.md](API-VERSION-3.md) for the full list of changes in API version 3.
|
||||
API version 3 is currently a beta API. It requires enabling `[beta_rpc_api]` in the xrpld configuration to use. See [API-VERSION-3.md](API-VERSION-3.md) for the full list of changes in API version 3.
|
||||
|
||||
## API Version 2
|
||||
|
||||
API version 2 is available in `rippled` version 2.0.0 and later. See [API-VERSION-2.md](API-VERSION-2.md) for the full list of changes in API version 2.
|
||||
API version 2 is available in `xrpld` version 2.0.0 and later. See [API-VERSION-2.md](API-VERSION-2.md) for the full list of changes in API version 2.
|
||||
|
||||
## API Version 1
|
||||
|
||||
This version is supported by all `rippled` versions. For WebSocket and HTTP JSON-RPC requests, it is currently the default API version used when no `api_version` is specified.
|
||||
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
|
||||
|
||||
@@ -28,6 +28,8 @@ This section contains changes targeting a future version.
|
||||
|
||||
### Additions
|
||||
|
||||
- `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.
|
||||
@@ -38,6 +40,16 @@ This section contains changes targeting a future version.
|
||||
### Bugfixes
|
||||
|
||||
- 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)
|
||||
- gRPC `GetLedgerDiff`: Fixed error message that incorrectly said "base ledger not validated" when the desired ledger was not validated. [#6730](https://github.com/XRPLF/rippled/pull/6730)
|
||||
- `account_channels`: The `destination_account` field now returns an error if the value is not a string. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- `subscribe`: The `taker` field in the `books` array now returns an error if the value is not a string. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- `account_info`: The `urlgravatar` field now uses HTTPS instead of HTTP. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- `ledger`: The `full`, `accounts`, `transactions`, `expand`, `binary`, `owner_funds`, and `queue` fields now return an error if the value is not a boolean. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- `ledger_data`: The `binary` field now returns an error if the value is not a boolean. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- `submit`: The `fail_hard` field now returns an error if the value is not a boolean. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- `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.0
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# API Version 2
|
||||
|
||||
API version 2 is available in `rippled` version 2.0.0 and later. To use this API, clients specify `"api_version" : 2` in each request.
|
||||
API version 2 is available in `xrpld` version 2.0.0 and later. To use this API, clients specify `"api_version" : 2` in each request.
|
||||
|
||||
For info about how [API versioning](https://xrpl.org/request-formatting.html#api-versioning) works, including examples, please view the [XLS-22d spec](https://github.com/XRPLF/XRPL-Standards/discussions/54). For details about the implementation of API versioning, view the [implementation PR](https://github.com/XRPLF/rippled/pull/3155). API versioning ensures existing integrations and users continue to receive existing behavior, while those that request a higher API version will experience new behavior.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# API Version 3
|
||||
|
||||
API version 3 is currently a **beta API**. It requires enabling `[beta_rpc_api]` in the rippled configuration to use. To use this API, clients specify `"api_version" : 3` in each request.
|
||||
API version 3 is currently a **beta API**. It requires enabling `[beta_rpc_api]` in the xrpld configuration to use. To use this API, clients specify `"api_version" : 3` in each request.
|
||||
|
||||
For info about how [API versioning](https://xrpl.org/request-formatting.html#api-versioning) works, including examples, please view the [XLS-22d spec](https://github.com/XRPLF/XRPL-Standards/discussions/54). For details about the implementation of API versioning, view the [implementation PR](https://github.com/XRPLF/rippled/pull/3155). API versioning ensures existing integrations and users continue to receive existing behavior, while those that request a higher API version will experience new behavior.
|
||||
|
||||
|
||||
421
BUILD.md
421
BUILD.md
@@ -1,26 +1,57 @@
|
||||
| :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). |
|
||||
| :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. |
|
||||
|
||||
> 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][3]
|
||||
> walkthrough.
|
||||
## Minimum Requirements
|
||||
|
||||
## Branches
|
||||
See [System Requirements](https://xrpl.org/system-requirements.html).
|
||||
|
||||
For a stable release, choose the `master` branch or one of the [tagged
|
||||
releases](https://github.com/XRPLF/rippled/releases).
|
||||
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
|
||||
git checkout master
|
||||
./bin/check-tools.sh
|
||||
```
|
||||
|
||||
For the latest release candidate, choose the `release` branch.
|
||||
`xrpld` is written in the C++23 dialect. The [tested compiler versions][cpp23-support] are:
|
||||
|
||||
```bash
|
||||
git checkout release
|
||||
```
|
||||
| Compiler | Version |
|
||||
| ----------- | --------------- |
|
||||
| GCC | 15.2 |
|
||||
| Clang | 22 |
|
||||
| Apple Clang | 17 |
|
||||
| MSVC | 19.44[^windows] |
|
||||
|
||||
## 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
|
||||
assurance, testing, and support. We also support Red Hat and use Debian
|
||||
internally.
|
||||
Our Linux CI tooling is distro-independent and uses a Nix-based environment, so it should be possible to build on other Linux distributions as well, although we have not tested them.
|
||||
|
||||
### macOS
|
||||
|
||||
Many `xrpld` engineers use macOS for development.
|
||||
|
||||
### Windows
|
||||
|
||||
Windows is used by some engineers for development only.
|
||||
|
||||
[^windows]: Windows is not recommended for production use.
|
||||
|
||||
## Steps
|
||||
|
||||
### Branches
|
||||
|
||||
For the latest set of untested features, or to contribute, choose the `develop`
|
||||
branch.
|
||||
@@ -29,55 +60,15 @@ branch.
|
||||
git checkout develop
|
||||
```
|
||||
|
||||
## Minimum Requirements
|
||||
For a release candidate, choose the relevant release branch, e.g.
|
||||
`release/3.2.x`.
|
||||
|
||||
See [System Requirements](https://xrpl.org/system-requirements.html).
|
||||
```bash
|
||||
git checkout release/3.2.x
|
||||
```
|
||||
|
||||
Building xrpld generally requires git, Python, Conan, CMake, and a C++
|
||||
compiler. Some guidance on setting up such a [C++ development environment can be
|
||||
found here](./docs/build/environment.md).
|
||||
|
||||
- [Python 3.11](https://www.python.org/downloads/), or higher
|
||||
- [Conan 2.17](https://conan.io/downloads.html)[^1], or higher
|
||||
- [CMake 3.22](https://cmake.org/download/), or higher
|
||||
|
||||
[^1]:
|
||||
It is possible to build with Conan 1.60+, but the instructions are
|
||||
significantly different, which is why we are not recommending it.
|
||||
|
||||
`xrpld` is written in the C++20 dialect and includes the `<concepts>` header.
|
||||
The [minimum compiler versions][2] required are:
|
||||
|
||||
| Compiler | Version |
|
||||
| ----------- | --------- |
|
||||
| GCC | 12 |
|
||||
| Clang | 16 |
|
||||
| Apple Clang | 16 |
|
||||
| MSVC | 19.44[^3] |
|
||||
|
||||
### Linux
|
||||
|
||||
The Ubuntu Linux distribution has received the highest level of quality
|
||||
assurance, testing, and support. We also support Red Hat and use Debian
|
||||
internally.
|
||||
|
||||
Here are [sample instructions for setting up a C++ development environment on
|
||||
Linux](./docs/build/environment.md#linux).
|
||||
|
||||
### Mac
|
||||
|
||||
Many xrpld engineers use macOS for development.
|
||||
|
||||
Here are [sample instructions for setting up a C++ development environment on
|
||||
macOS](./docs/build/environment.md#macos).
|
||||
|
||||
### Windows
|
||||
|
||||
Windows is used by some engineers for development only.
|
||||
|
||||
[^3]: Windows is not recommended for production use.
|
||||
|
||||
## Steps
|
||||
For a stable release, choose one of the [tagged
|
||||
releases](https://github.com/XRPLF/rippled/releases).
|
||||
|
||||
### Set Up Conan
|
||||
|
||||
@@ -86,18 +77,11 @@ 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][3] walkthrough.
|
||||
[Getting Started][conan-getting-started] walkthrough.
|
||||
|
||||
#### Conan lockfile
|
||||
#### Profiles
|
||||
|
||||
To achieve reproducible dependencies, we use a [Conan lockfile](https://docs.conan.io/2/tutorial/versioning/lockfiles.html),
|
||||
which has to be updated every time dependencies change.
|
||||
|
||||
Please see the [instructions on how to regenerate the lockfile](conan/lockfile/README.md).
|
||||
|
||||
#### Default profile
|
||||
|
||||
We recommend that you import the provided `conan/profiles/default` profile:
|
||||
We recommend that you install our Conan profiles:
|
||||
|
||||
```bash
|
||||
conan config install conan/profiles/ -tf $(conan config home)/profiles/
|
||||
@@ -109,222 +93,15 @@ You can check your Conan profile by running:
|
||||
conan profile show
|
||||
```
|
||||
|
||||
#### Custom profile
|
||||
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).
|
||||
|
||||
If the default profile does not work for you and you do not yet have a Conan
|
||||
profile, you can create one by running:
|
||||
#### Add xrplf remote
|
||||
|
||||
Run the following command to add the `xrplf` remote, which hosts some of our dependencies:
|
||||
|
||||
```bash
|
||||
conan profile detect
|
||||
```
|
||||
|
||||
You may need to make changes to the profile to suit your environment. You can
|
||||
refer to the provided `conan/profiles/default` profile for inspiration, and you
|
||||
may also need to apply the required [tweaks](#conan-profile-tweaks) to this
|
||||
default profile.
|
||||
|
||||
### Patched recipes
|
||||
|
||||
Occasionally, we need patched recipes or recipes not present in Conan Center.
|
||||
We maintain a fork of the Conan Center Index
|
||||
[here](https://github.com/XRPLF/conan-center-index/) containing the modified and newly added recipes.
|
||||
|
||||
To ensure our patched recipes are used, you must add our Conan remote at a
|
||||
higher index than the default Conan Center remote, so it is consulted first. You
|
||||
can do this by running:
|
||||
|
||||
```bash
|
||||
conan remote add --index 0 xrplf https://conan.ripplex.io
|
||||
```
|
||||
|
||||
Alternatively, you can pull our recipes from the repository and export them locally:
|
||||
|
||||
```bash
|
||||
# Define which recipes to export.
|
||||
recipes=('abseil' 'ed25519' 'grpc' 'm4' 'mpt-crypto' 'nudb' 'openssl' 'secp256k1' 'snappy' 'soci' 'wasm-xrplf' 'wasmi')
|
||||
|
||||
# Selectively check out the recipes from our CCI fork.
|
||||
cd external
|
||||
mkdir -p conan-center-index
|
||||
cd conan-center-index
|
||||
git init
|
||||
git remote add origin git@github.com:XRPLF/conan-center-index.git
|
||||
git sparse-checkout init
|
||||
for recipe in "${recipes[@]}"; do
|
||||
echo "Checking out recipe '${recipe}'..."
|
||||
git sparse-checkout add recipes/${recipe}
|
||||
done
|
||||
git fetch origin master
|
||||
git checkout master
|
||||
|
||||
./export_all.sh
|
||||
cd ../../
|
||||
```
|
||||
|
||||
In the case we switch to a newer version of a dependency that still requires a
|
||||
patch or add a new dependency, it will be necessary for you to pull in the changes and re-export the
|
||||
updated dependencies with the newer version. However, if we switch to a newer
|
||||
version that no longer requires a patch, no action is required on your part, as
|
||||
the new recipe will be automatically pulled from the official Conan Center.
|
||||
|
||||
> [!NOTE]
|
||||
> You might need to add `--lockfile=""` to your `conan install` command
|
||||
> to avoid automatic use of the existing `conan.lock` file when you run
|
||||
> `conan export` manually on your machine
|
||||
>
|
||||
> This is not recommended though, as you might end up using different revisions of recipes.
|
||||
|
||||
### Conan profile tweaks
|
||||
|
||||
#### Missing compiler version
|
||||
|
||||
If you see an error similar to the following after running `conan profile show`:
|
||||
|
||||
```bash
|
||||
ERROR: Invalid setting '17' is not a valid 'settings.compiler.version' value.
|
||||
Possible values are ['5.0', '5.1', '6.0', '6.1', '7.0', '7.3', '8.0', '8.1',
|
||||
'9.0', '9.1', '10.0', '11.0', '12.0', '13', '13.0', '13.1', '14', '14.0', '15',
|
||||
'15.0', '16', '16.0']
|
||||
Read "http://docs.conan.io/2/knowledge/faq.html#error-invalid-setting"
|
||||
```
|
||||
|
||||
you need to add your compiler to the list of compiler versions in
|
||||
`$(conan config home)/settings_user.yml`, by adding the required version number(s)
|
||||
to the `version` array specific for your compiler. For example:
|
||||
|
||||
```yaml
|
||||
compiler:
|
||||
apple-clang:
|
||||
version: ["17.0"]
|
||||
```
|
||||
|
||||
#### Multiple compilers
|
||||
|
||||
If you have multiple compilers installed, make sure to select the one to use in
|
||||
your default Conan configuration **before** running `conan profile detect`, by
|
||||
setting the `CC` and `CXX` environment variables.
|
||||
|
||||
For example, if you are running MacOS and have [homebrew
|
||||
LLVM@18](https://formulae.brew.sh/formula/llvm@18), and want to use it as a
|
||||
compiler in the new Conan profile:
|
||||
|
||||
```bash
|
||||
export CC=$(brew --prefix llvm@18)/bin/clang
|
||||
export CXX=$(brew --prefix llvm@18)/bin/clang++
|
||||
conan profile detect
|
||||
```
|
||||
|
||||
You should also explicitly set the path to the compiler in the profile file,
|
||||
which helps to avoid errors when `CC` and/or `CXX` are set and disagree with the
|
||||
selected Conan profile. For example:
|
||||
|
||||
```text
|
||||
[conf]
|
||||
tools.build:compiler_executables={'c':'/usr/bin/gcc','cpp':'/usr/bin/g++'}
|
||||
```
|
||||
|
||||
#### Multiple profiles
|
||||
|
||||
You can manage multiple Conan profiles in the directory
|
||||
`$(conan config home)/profiles`, for example renaming `default` to a different
|
||||
name and then creating a new `default` profile for a different compiler.
|
||||
|
||||
#### Select language
|
||||
|
||||
The default profile created by Conan will typically select different C++ dialect
|
||||
than C++20 used by this project. You should set `20` in the profile line
|
||||
starting with `compiler.cppstd=`. For example:
|
||||
|
||||
```bash
|
||||
sed -i.bak -e 's|^compiler\.cppstd=.*$|compiler.cppstd=20|' $(conan config home)/profiles/default
|
||||
```
|
||||
|
||||
#### Select standard library in Linux
|
||||
|
||||
**Linux** developers will commonly have a default Conan [profile][] that
|
||||
compiles with GCC and links with libstdc++. If you are linking with libstdc++
|
||||
(see profile setting `compiler.libcxx`), then you will need to choose the
|
||||
`libstdc++11` ABI:
|
||||
|
||||
```bash
|
||||
sed -i.bak -e 's|^compiler\.libcxx=.*$|compiler.libcxx=libstdc++11|' $(conan config home)/profiles/default
|
||||
```
|
||||
|
||||
#### Select architecture and runtime in Windows
|
||||
|
||||
**Windows** developers may need to use the x64 native build tools. An easy way
|
||||
to do that is to run the shortcut "x64 Native Tools Command Prompt" for the
|
||||
version of Visual Studio that you have installed.
|
||||
|
||||
Windows developers must also build `xrpld` and its dependencies for the x64
|
||||
architecture:
|
||||
|
||||
```bash
|
||||
sed -i.bak -e 's|^arch=.*$|arch=x86_64|' $(conan config home)/profiles/default
|
||||
```
|
||||
|
||||
**Windows** developers also must select static runtime:
|
||||
|
||||
```bash
|
||||
sed -i.bak -e 's|^compiler\.runtime=.*$|compiler.runtime=static|' $(conan config home)/profiles/default
|
||||
```
|
||||
|
||||
#### Clang workaround for grpc
|
||||
|
||||
If your compiler is clang, version 19 or later, or apple-clang, version 17 or
|
||||
later, you may encounter a compilation error while building the `grpc`
|
||||
dependency:
|
||||
|
||||
```text
|
||||
In file included from .../lib/promise/try_seq.h:26:
|
||||
.../lib/promise/detail/basic_seq.h:499:38: error: a template argument list is expected after a name prefixed by the template keyword [-Wmissing-template-arg-list-after-template-kw]
|
||||
499 | Traits::template CallSeqFactory(f_, *cur_, std::move(arg)));
|
||||
| ^
|
||||
```
|
||||
|
||||
The workaround for this error is to add two lines to profile:
|
||||
|
||||
```text
|
||||
[conf]
|
||||
tools.build:cxxflags=['-Wno-missing-template-arg-list-after-template-kw']
|
||||
```
|
||||
|
||||
#### Workaround for gcc 12
|
||||
|
||||
If your compiler is gcc, version 12, and you have enabled `werr` option, you may
|
||||
encounter a compilation error such as:
|
||||
|
||||
```text
|
||||
/usr/include/c++/12/bits/char_traits.h:435:56: error: 'void* __builtin_memcpy(void*, const void*, long unsigned int)' accessing 9223372036854775810 or more bytes at offsets [2, 9223372036854775807] and 1 may overlap up to 9223372036854775813 bytes at offset -3 [-Werror=restrict]
|
||||
435 | return static_cast<char_type*>(__builtin_memcpy(__s1, __s2, __n));
|
||||
| ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~
|
||||
cc1plus: all warnings being treated as errors
|
||||
```
|
||||
|
||||
The workaround for this error is to add two lines to your profile:
|
||||
|
||||
```text
|
||||
[conf]
|
||||
tools.build:cxxflags=['-Wno-restrict']
|
||||
```
|
||||
|
||||
#### Workaround for clang 16
|
||||
|
||||
If your compiler is clang, version 16, you may encounter compilation error such
|
||||
as:
|
||||
|
||||
```text
|
||||
In file included from .../boost/beast/websocket/stream.hpp:2857:
|
||||
.../boost/beast/websocket/impl/read.hpp:695:17: error: call to 'async_teardown' is ambiguous
|
||||
async_teardown(impl.role, impl.stream(),
|
||||
^~~~~~~~~~~~~~
|
||||
```
|
||||
|
||||
The workaround for this error is to add two lines to your profile:
|
||||
|
||||
```text
|
||||
[conf]
|
||||
tools.build:cxxflags=['-DBOOST_ASIO_DISABLE_CONCEPTS']
|
||||
conan remote add --index 0 --force xrplf https://conan.xrplf.org/repository/conan/
|
||||
```
|
||||
|
||||
### Set Up Ccache
|
||||
@@ -333,14 +110,7 @@ To speed up repeated compilations, we recommend that you install
|
||||
[ccache](https://ccache.dev), a tool that wraps your compiler so that it can
|
||||
cache build objects locally.
|
||||
|
||||
#### Linux
|
||||
|
||||
You can install it using the package manager, e.g. `sudo apt install ccache`
|
||||
(Ubuntu) or `sudo dnf install ccache` (RHEL).
|
||||
|
||||
#### macOS
|
||||
|
||||
You can install it using Homebrew, i.e. `brew install ccache`.
|
||||
On Linux and macOS, `ccache` is included in the [Nix development shell](./docs/build/nix.md).
|
||||
|
||||
#### Windows
|
||||
|
||||
@@ -427,16 +197,19 @@ install ccache --version 4.11.3 --allow-downgrade`.
|
||||
Single-config generators:
|
||||
|
||||
```
|
||||
cmake --build .
|
||||
cmake --build . --parallel N
|
||||
```
|
||||
|
||||
Multi-config generators:
|
||||
|
||||
```
|
||||
cmake --build . --config Release
|
||||
cmake --build . --config Debug
|
||||
cmake --build . --config Release --parallel N
|
||||
cmake --build . --config Debug --parallel N
|
||||
```
|
||||
|
||||
Replace the `--parallel` parameter N with the desired number of parallel jobs. A common starting point is half of the number of available CPU
|
||||
cores.
|
||||
|
||||
5. Test xrpld.
|
||||
|
||||
Single-config generators:
|
||||
@@ -459,6 +232,21 @@ install ccache --version 4.11.3 --allow-downgrade`.
|
||||
The location of `xrpld` binary in your build directory depends on your
|
||||
CMake generator. Pass `--help` to see the rest of the command line options.
|
||||
|
||||
## Code generation
|
||||
|
||||
The protocol wrapper classes in `include/xrpl/protocol_autogen/` are generated
|
||||
from macro definition files in `include/xrpl/protocol/detail/`. If you modify
|
||||
the macro files (e.g. `transactions.macro`, `ledger_entries.macro`) or the
|
||||
generation scripts/templates in `cmake/scripts/codegen/`, you need to regenerate the
|
||||
files:
|
||||
|
||||
```
|
||||
cmake --build . --target setup_code_gen # create venv and install dependencies (once)
|
||||
cmake --build . --target code_gen # regenerate code
|
||||
```
|
||||
|
||||
The regenerated files should be committed alongside your changes.
|
||||
|
||||
## Coverage report
|
||||
|
||||
The coverage report is intended for developers using compilers GCC
|
||||
@@ -515,23 +303,23 @@ stored inside the build directory, as either of:
|
||||
## Sanitizers
|
||||
|
||||
To build dependencies and xrpld with sanitizer instrumentation, set the
|
||||
`SANITIZERS` environment variable (only once before running conan and cmake) and use the `sanitizers` profile in conan:
|
||||
`SANITIZERS` environment variable when running `conan install` and use the `sanitizers` profile:
|
||||
|
||||
```bash
|
||||
export SANITIZERS=address,undefinedbehavior
|
||||
|
||||
conan install .. --output-folder . --profile:all sanitizers --build missing --settings build_type=Debug
|
||||
|
||||
cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Debug -Dxrpld=ON -Dtests=ON ..
|
||||
```
|
||||
|
||||
You can then build and test as usual, with the generated `xrpld` binary containing the sanitizer instrumentation. When you run it, it will report any sanitizer errors it detects in the console output.
|
||||
|
||||
See [Sanitizers docs](./docs/build/sanitizers.md) for more details.
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Default Value | Description |
|
||||
| ---------- | ------------- | -------------------------------------------------------------- |
|
||||
| `assert` | OFF | Enable assertions. |
|
||||
| `assert` | OFF | Force enabling assertions. |
|
||||
| `coverage` | OFF | Prepare the coverage report. |
|
||||
| `tests` | OFF | Build tests. |
|
||||
| `unity` | OFF | Configure a unity build. |
|
||||
@@ -539,7 +327,7 @@ See [Sanitizers docs](./docs/build/sanitizers.md) for more details.
|
||||
| `werr` | OFF | Treat compilation warnings as errors |
|
||||
| `wextra` | OFF | Enable additional compilation warnings |
|
||||
|
||||
[Unity builds][5] may be faster for the first build (at the cost of much more
|
||||
[Unity builds][unity-build] may be faster for the first build (at the cost of much more
|
||||
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.
|
||||
@@ -565,14 +353,14 @@ After any updates or changes to dependencies, you may need to do the following:
|
||||
conan remove '*'
|
||||
```
|
||||
|
||||
3. Re-run [conan export](#patched-recipes) if needed.
|
||||
4. [Regenerate lockfile](#conan-lockfile).
|
||||
3. Re-run [conan export](./docs/build/advanced_conan.md#patched-recipes) if needed.
|
||||
4. [Regenerate lockfile](./docs/build/advanced_conan.md#conan-lockfile).
|
||||
5. Re-run [conan install](#build-and-test).
|
||||
|
||||
#### 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 or re-run `conan export` for [patched recipes](#patched-recipes).
|
||||
please [add `xrplf` remote](#add-xrplf-remote) or re-run `conan export` for [patched recipes](./docs/build/advanced_conan.md#patched-recipes).
|
||||
|
||||
### `protobuf/port_def.inc` file not found
|
||||
|
||||
@@ -592,28 +380,9 @@ 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`
|
||||
|
||||
## Add a Dependency
|
||||
|
||||
If you want to experiment with a new package, follow these steps:
|
||||
|
||||
1. Search for the package on [Conan Center](https://conan.io/center/).
|
||||
2. Modify [`conanfile.py`](./conanfile.py):
|
||||
- Add a version of the package to the `requires` property.
|
||||
- Change any default options for the package by adding them to the
|
||||
`default_options` property (with syntax `'$package:$option': $value`).
|
||||
3. Modify [`CMakeLists.txt`](./CMakeLists.txt):
|
||||
- Add a call to `find_package($package REQUIRED)`.
|
||||
- Link a library from the package to the target `ripple_libs`
|
||||
(search for the existing call to `target_link_libraries(ripple_libs INTERFACE ...)`).
|
||||
4. Start coding! Don't forget to include whatever headers you need from the package.
|
||||
|
||||
[1]: https://github.com/conan-io/conan-center-index/issues/13168
|
||||
[2]: https://en.cppreference.com/w/cpp/compiler_support/20
|
||||
[3]: https://docs.conan.io/en/latest/getting_started.html
|
||||
[5]: https://en.wikipedia.org/wiki/Unity_build
|
||||
[6]: https://github.com/boostorg/beast/issues/2648
|
||||
[7]: https://github.com/boostorg/beast/issues/2661
|
||||
[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
|
||||
[python-pip]: https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/
|
||||
[build_type]: https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html
|
||||
[profile]: https://docs.conan.io/en/latest/reference/profiles.html
|
||||
|
||||
@@ -15,7 +15,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
|
||||
project(xrpl)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
@@ -57,6 +57,8 @@ if(target)
|
||||
)
|
||||
endif()
|
||||
|
||||
include(PatchNixBinary)
|
||||
|
||||
include(XrplSanity)
|
||||
include(XrplVersion)
|
||||
include(XrplSettings)
|
||||
@@ -117,6 +119,18 @@ if(rocksdb)
|
||||
target_link_libraries(xrpl_libs INTERFACE RocksDB::rocksdb)
|
||||
endif()
|
||||
|
||||
# OpenTelemetry distributed tracing (optional).
|
||||
# When ON, links against opentelemetry-cpp and defines XRPL_ENABLE_TELEMETRY
|
||||
# so that SpanGuard factory methods produce real OTel spans.
|
||||
# When OFF (default), all tracing code compiles to no-ops with zero overhead.
|
||||
# Enable via: conan install -o telemetry=True, or cmake -Dtelemetry=ON.
|
||||
option(telemetry "Enable OpenTelemetry tracing" ON)
|
||||
if(telemetry)
|
||||
find_package(opentelemetry-cpp CONFIG REQUIRED)
|
||||
add_compile_definitions(XRPL_ENABLE_TELEMETRY)
|
||||
message(STATUS "OpenTelemetry tracing enabled")
|
||||
endif()
|
||||
|
||||
# Work around changes to Conan recipe for now.
|
||||
if(TARGET nudb::core)
|
||||
set(nudb nudb::core)
|
||||
@@ -132,7 +146,9 @@ if(coverage)
|
||||
endif()
|
||||
|
||||
include(XrplCore)
|
||||
include(XrplProtocolAutogen)
|
||||
include(XrplInstall)
|
||||
include(XrplPackaging)
|
||||
include(XrplValidatorKeys)
|
||||
|
||||
if(tests)
|
||||
|
||||
106
CONTRIBUTING.md
106
CONTRIBUTING.md
@@ -14,9 +14,9 @@ The following branches exist in the main project repository:
|
||||
|
||||
- `develop`: The latest set of unreleased features, and the most common
|
||||
starting point for contributions.
|
||||
- `release`: The latest beta release or release candidate.
|
||||
- `master`: The latest stable release.
|
||||
- `gh-pages`: The documentation for this project, built by Doxygen.
|
||||
- `release/*` (e.g. `release/3.2.x`): Release branches, one per release line,
|
||||
holding the latest release candidate, or stable release for that line.
|
||||
Stable releases are published as [tagged releases](https://github.com/XRPLF/rippled/releases).
|
||||
|
||||
The tip of each branch must be signed. In order for GitHub to sign a
|
||||
squashed commit that it builds from your pull request, GitHub must know
|
||||
@@ -130,11 +130,9 @@ tl;dr
|
||||
## Pull requests
|
||||
|
||||
In general, pull requests use `develop` as the base branch.
|
||||
The exceptions are
|
||||
|
||||
- Fixes and improvements to a release candidate use `release` as the
|
||||
base.
|
||||
- Hotfixes use `master` as the base.
|
||||
The exceptions are fixes, improvements, and hotfixes for an existing release,
|
||||
which use that release's branch (e.g. `release/3.2.x`) as the base.
|
||||
|
||||
If your changes are not quite ready, but you want to make it easily available
|
||||
for preliminary examination or review, you can create a "Draft" pull request.
|
||||
@@ -216,7 +214,7 @@ coherent rather than a set of _thou shalt not_ commandments.
|
||||
|
||||
## Formatting
|
||||
|
||||
All code must conform to `clang-format` version 21,
|
||||
All code must conform to `clang-format` version 22,
|
||||
according to the settings in [`.clang-format`](./.clang-format),
|
||||
unless the result would be unreasonably difficult to read or maintain.
|
||||
To demarcate lines that should be left as-is, surround them with comments like
|
||||
@@ -261,25 +259,105 @@ This ensures that configuration changes don't introduce new warnings across the
|
||||
|
||||
### Installing clang-tidy
|
||||
|
||||
See the [environment setup guide](./docs/build/environment.md#clang-tidy) for platform-specific installation instructions.
|
||||
See the [environment setup guide](./docs/build/environment.md#clang-tidy) for how to get clang-tidy.
|
||||
|
||||
### 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.
|
||||
|
||||
#### Via pre-commit (recommended)
|
||||
|
||||
If you have already installed the pre-commit hooks (see above), you can run clang-tidy on your staged files using:
|
||||
|
||||
```
|
||||
TIDY=1 pre-commit run clang-tidy
|
||||
```
|
||||
|
||||
This runs clang-tidy locally with the same configuration/flags as CI, scoped to your staged C++ files. The `TIDY=1` environment variable is required to opt in — without it the hook is skipped.
|
||||
|
||||
You can also have clang-tidy run automatically on every `git commit` by setting `TIDY=1` in your shell environment:
|
||||
|
||||
```
|
||||
export TIDY=1
|
||||
```
|
||||
|
||||
With this set, the hook will run as part of `git commit` alongside the other pre-commit checks.
|
||||
|
||||
#### Manually
|
||||
|
||||
Then run clang-tidy on your local changes:
|
||||
|
||||
```
|
||||
run-clang-tidy -p build src include tests
|
||||
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:
|
||||
|
||||
```
|
||||
run-clang-tidy -p build -fix src include tests
|
||||
run-clang-tidy -p build -quiet -fix -allow-no-checks src tests
|
||||
```
|
||||
|
||||
## Telemetry span attribute naming
|
||||
|
||||
OpenTelemetry span attribute keys follow these rules so they stay consistent
|
||||
across the code, the OTel collector, Tempo, Grafana dashboards, and docs. The
|
||||
constants in the `*SpanNames.h` headers are the single source of truth; every
|
||||
other layer must match them. A CI check enforces this end to end.
|
||||
|
||||
1. Per-span unique attribute: bare field name — allowed when the field is
|
||||
recorded by a single span/workflow, so the span name already supplies the
|
||||
domain (e.g. `command`, `local`, `version` on `rpc.command` / `tx.process`).
|
||||
2. Shared attribute (same concept on more than one span): ONE key, reused
|
||||
verbatim on every span that records it — the span name tells the occurrences
|
||||
apart, so no per-emitter prefix is added. Pick the name by the field's
|
||||
meaning: a property of a domain object keeps that object's bare field name
|
||||
(`ledger_hash`, `ledger_seq`, `tx_hash`, `peer_id`, `full_validation`); a
|
||||
field already qualified by a sub-kind keeps that qualifier on every emitter
|
||||
(`proposal_trusted` on both `consensus.proposal.receive` and
|
||||
`peer.proposal.receive`; `validation_trusted` likewise). Define it once in
|
||||
the base `SpanNames.h` `namespace attr` block and re-export (`using`) it from
|
||||
each domain header, so all emitters share the exact string.
|
||||
3. Collision qualifier: `<domain>_<field>` — only when a bare name would collide
|
||||
with a DIFFERENT concept in the shared spanmetrics label space, or with the
|
||||
OTel-reserved `status` key (e.g. `rpc_status`, `grpc_status`,
|
||||
`consensus_state`, `consensus_round`). This disambiguates distinct concepts
|
||||
that share a word; it is NOT used to tag the same concept with the workflow
|
||||
that emitted it — that is rule 2 (one shared name).
|
||||
4. Resource attribute: dotted `xrpl.<subsystem>.<field>` — reserved ONLY for
|
||||
process/network identity set once at startup (`xrpl.network.id`,
|
||||
`xrpl.network.type`). Never use the dotted `xrpl.` form for span attributes.
|
||||
5. Span names use `<subsystem>[.<component>]` (dotted). Only attribute _keys_
|
||||
follow rules 1–4.
|
||||
|
||||
All attribute keys are `lower_snake_case` (lowercase letters, digits, and
|
||||
underscores; each dot-separated segment of a resource key likewise). No
|
||||
camelCase, uppercase, or spaces.
|
||||
|
||||
Standard OpenTelemetry semantic-convention keys keep their canonical dotted
|
||||
form (e.g. `service.*` resource attributes, `http.*` span attributes); the
|
||||
"no dotted form" rule above applies to xrpl-custom keys, not to OTel-standard
|
||||
conventions.
|
||||
|
||||
Always reference the `*SpanNames.h` constants for attribute keys and span
|
||||
names — never pass a string literal as a key or as a `span`/`childSpan` name
|
||||
argument. (Attribute _values_ may be runtime data.)
|
||||
|
||||
These rules are enforced by `.github/scripts/otel-naming/check_otel_naming.py`,
|
||||
run in CI on every pull request. The check derives the set of valid keys
|
||||
directly from the `*SpanNames.h` constants and the resource attributes the code
|
||||
registers, so there is no separate list to keep in sync. It cross-validates the
|
||||
collector, Tempo, dashboards, and docs against those keys, and each rule runs
|
||||
only when the file it needs is present — so it works whether telemetry changes
|
||||
land in one pull request or several. Run it locally with:
|
||||
|
||||
```
|
||||
python .github/scripts/otel-naming/check_otel_naming.py
|
||||
```
|
||||
|
||||
See [.github/scripts/otel-naming/README.md](.github/scripts/otel-naming/README.md)
|
||||
for the full rule list.
|
||||
|
||||
## Contracts and instrumentation
|
||||
|
||||
We are using [Antithesis](https://antithesis.com/) for continuous fuzzing,
|
||||
@@ -328,8 +406,8 @@ For this reason:
|
||||
- Contract description for `UNREACHABLE` should describe the _unexpected_
|
||||
situation which caused the line to have been reached.
|
||||
- Example good name for an
|
||||
`UNREACHABLE` macro `"Json::operator==(Value, Value) : invalid type"`; example
|
||||
good name for an `XRPL_ASSERT` macro `"Json::Value::asCString : valid type"`.
|
||||
`UNREACHABLE` macro `"json::operator==(Value, Value) : invalid type"`; example
|
||||
good name for an `XRPL_ASSERT` macro `"json::Value::asCString : valid type"`.
|
||||
- Example **bad** name
|
||||
`"RFC1751::insert(char* s, int x, int start, int length) : length is greater than or equal zero"`
|
||||
(missing namespace, unnecessary full function signature, description too verbose).
|
||||
@@ -533,7 +611,7 @@ All releases, including release candidates and betas, are handled
|
||||
differently from typical PRs. Most importantly, never use
|
||||
the Github UI to merge a release.
|
||||
|
||||
Rippled uses a linear workflow model that can be summarized as:
|
||||
Xrpld uses a linear workflow model that can be summarized as:
|
||||
|
||||
1. In between releases, developers work against the `develop` branch.
|
||||
2. Periodically, a maintainer will build and tag a beta version from
|
||||
|
||||
565
OpenTelemetryPlan/00-tracing-fundamentals.md
Normal file
565
OpenTelemetryPlan/00-tracing-fundamentals.md
Normal file
@@ -0,0 +1,565 @@
|
||||
# Distributed Tracing Fundamentals
|
||||
|
||||
> **Parent Document**: [OpenTelemetryPlan.md](./OpenTelemetryPlan.md)
|
||||
> **Next**: [Architecture Analysis](./01-architecture-analysis.md)
|
||||
|
||||
---
|
||||
|
||||
## What is Distributed Tracing?
|
||||
|
||||
Distributed tracing is a method for tracking data objects as they flow through distributed systems. In a network like XRP Ledger, a single transaction touches multiple independent nodes—each with no shared memory or logging. Distributed tracing connects these dots.
|
||||
|
||||
**Without tracing:** You see isolated logs on each node with no way to correlate them.
|
||||
|
||||
**With tracing:** You see the complete journey of a transaction or an event across all nodes it touched.
|
||||
|
||||
---
|
||||
|
||||
## Actors and Actions at a Glance
|
||||
|
||||
### Actors
|
||||
|
||||
| Who (Plain English) | Technical Term |
|
||||
| ---------------------------------------------- | --------------- |
|
||||
| A single unit of work being tracked | Span |
|
||||
| The complete journey of a request | Trace |
|
||||
| Data that links spans across services | Trace Context |
|
||||
| Code that creates spans and propagates context | Instrumentation |
|
||||
| Service that receives and processes traces | Collector |
|
||||
| Storage and visualization system | Backend (Tempo) |
|
||||
| Decision logic for which traces to keep | Sampler |
|
||||
|
||||
### Actions
|
||||
|
||||
| What Happens (Plain English) | Technical Term |
|
||||
| --------------------------------------- | ----------------------- |
|
||||
| Start tracking a new operation | Create a Span |
|
||||
| Connect a child operation to its parent | Set `parent_span_id` |
|
||||
| Group all related operations together | Share a `trace_id` |
|
||||
| Pass tracking data between services | Context Propagation |
|
||||
| Decide whether to record a trace | Sampling (Head or Tail) |
|
||||
| Send completed traces to storage | Export (OTLP) |
|
||||
|
||||
---
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### 1. Trace
|
||||
|
||||
A **trace** represents the entire journey of a request through the system. It has a unique `trace_id` that stays constant across all nodes.
|
||||
|
||||
```
|
||||
Trace ID: abc123
|
||||
├── Node A: received transaction
|
||||
├── Node B: relayed transaction
|
||||
├── Node C: included in consensus
|
||||
└── Node D: applied to ledger
|
||||
```
|
||||
|
||||
### 2. Span
|
||||
|
||||
A **span** represents a single unit of work within a trace. Each span has:
|
||||
|
||||
| Attribute | Description | Example |
|
||||
| ---------------- | -------------------------------- | -------------------------- |
|
||||
| `trace_id` | Identifies the trace | `event123` |
|
||||
| `span_id` | Unique identifier | `span456` |
|
||||
| `parent_span_id` | Parent span (if any) | `p_span123` |
|
||||
| `name` | Operation name | `rpc.submit` |
|
||||
| `start_time` | When work began (local time) | `2024-01-15T10:30:00Z` |
|
||||
| `end_time` | When work completed (local time) | `2024-01-15T10:30:00.050Z` |
|
||||
| `attributes` | Key-value metadata | `tx_hash=ABC...` |
|
||||
| `status` | OK, ERROR MSG | `OK` |
|
||||
|
||||
### 3. Trace Context
|
||||
|
||||
**Trace context** is the data that propagates between services to link spans together. It contains:
|
||||
|
||||
- `trace_id` - The trace this span belongs to
|
||||
- `span_id` - The current span (becomes parent for child spans)
|
||||
- `trace_flags` - Sampling decisions
|
||||
|
||||
---
|
||||
|
||||
## How Spans Form a Trace
|
||||
|
||||
Spans have parent-child relationships forming a tree structure:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph trace["Trace: abc123"]
|
||||
A["tx.submit<br/>span_id: 001<br/>50ms"] --> B["tx.validate<br/>span_id: 002<br/>5ms"]
|
||||
A --> C["tx.relay<br/>span_id: 003<br/>10ms"]
|
||||
A --> D["tx.apply<br/>span_id: 004<br/>30ms"]
|
||||
D --> E["ledger.update<br/>span_id: 005<br/>20ms"]
|
||||
end
|
||||
|
||||
style A fill:#0d47a1,stroke:#082f6a,color:#ffffff
|
||||
style B fill:#1b5e20,stroke:#0d3d14,color:#ffffff
|
||||
style C fill:#1b5e20,stroke:#0d3d14,color:#ffffff
|
||||
style D fill:#1b5e20,stroke:#0d3d14,color:#ffffff
|
||||
style E fill:#bf360c,stroke:#8c2809,color:#ffffff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **tx.submit (blue, root)**: The top-level span representing the entire transaction submission; all other spans are its descendants.
|
||||
- **tx.validate, tx.relay, tx.apply (green)**: Direct children of tx.submit, representing the three main stages -- validation, relay to peers, and application to the ledger.
|
||||
- **ledger.update (red)**: A grandchild span nested under tx.apply, representing the actual ledger state mutation triggered by applying the transaction.
|
||||
- **Arrows (parent to child)**: Each arrow indicates a parent-child span relationship where the parent's completion depends on the child finishing.
|
||||
|
||||
The same trace visualized as a **timeline (Gantt chart)**:
|
||||
|
||||
```
|
||||
Time → 0ms 10ms 20ms 30ms 40ms 50ms
|
||||
├───────────────────────────────────────────┤
|
||||
tx.submit│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│
|
||||
├─────┤
|
||||
tx.valid │▓▓▓▓▓│
|
||||
│ ├──────────┤
|
||||
tx.relay │ │▓▓▓▓▓▓▓▓▓▓│
|
||||
│ ├────────────────────────────┤
|
||||
tx.apply │ │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│
|
||||
│ ├──────────────────┤
|
||||
ledger │ │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Span Relationships
|
||||
|
||||
Spans don't always form simple parent-child trees. Distributed tracing defines several relationship types to capture different causal patterns:
|
||||
|
||||
### 1. Parent-Child (ChildOf)
|
||||
|
||||
The default relationship. The parent span **depends on** or **contains** the child span. The child runs within the scope of the parent.
|
||||
|
||||
```
|
||||
tx.submit (parent)
|
||||
├── tx.validate (child) ← parent waits for this
|
||||
├── tx.relay (child) ← parent waits for this
|
||||
└── tx.apply (child) ← parent waits for this
|
||||
```
|
||||
|
||||
**When to use:** Synchronous calls, nested operations, any case where the parent's completion depends on the child.
|
||||
|
||||
### 2. Follows-From
|
||||
|
||||
A causal relationship where the first span **triggers** the second, but does **not wait** for it. The originator fires and moves on.
|
||||
|
||||
```
|
||||
Time →
|
||||
|
||||
tx.receive [=======]
|
||||
↓ triggers (follows-from)
|
||||
tx.relay [===========] ← runs independently
|
||||
```
|
||||
|
||||
**When to use:** Asynchronous jobs, queued work, fire-and-forget patterns. For example, a node receives a transaction and queues it for relay — the relay span _follows from_ the receive span but the receiver doesn't wait for relaying to complete.
|
||||
|
||||
> **OpenTracing** defined `FollowsFrom` as a first-class reference type alongside `ChildOf`.
|
||||
> **OpenTelemetry** represents this using **Span Links** with descriptive attributes instead (see below).
|
||||
|
||||
### 3. Span Links (Cross-Trace and Non-Hierarchical)
|
||||
|
||||
Links connect spans that are **causally related but not in a parent-child hierarchy**. Unlike parent-child, links can cross trace boundaries.
|
||||
|
||||
```
|
||||
Trace A Trace B
|
||||
────── ──────
|
||||
batch.schedule batch.execute
|
||||
├─ item.enqueue (span X) ┌──► process.item
|
||||
├─ item.enqueue (span Y) ───┤ (links to X, Y, Z)
|
||||
├─ item.enqueue (span Z) └──►
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
|
||||
| Pattern | Description |
|
||||
| -------------------- | --------------------------------------------------------------------------- |
|
||||
| **Batch processing** | A batch span links back to all individual spans that contributed to it |
|
||||
| **Fan-in** | An aggregation span links to the multiple producer spans it merges |
|
||||
| **Fan-out** | Multiple downstream spans link back to the single span that triggered them |
|
||||
| **Async handoff** | A deferred job links back to the request that queued it (follows-from) |
|
||||
| **Cross-trace** | Correlating spans across independent traces (e.g., retries, related events) |
|
||||
|
||||
**Link structure:** Each link carries the target span's context plus optional attributes:
|
||||
|
||||
```
|
||||
Link {
|
||||
trace_id: <target trace>
|
||||
span_id: <target span>
|
||||
attributes: { "link.description": "triggered by batch scheduler" }
|
||||
}
|
||||
```
|
||||
|
||||
### Relationship Summary
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph parent_child["Parent-Child"]
|
||||
direction TB
|
||||
P["Parent"] --> C["Child"]
|
||||
end
|
||||
|
||||
subgraph follows_from["Follows-From"]
|
||||
direction TB
|
||||
A["Span A"] -.->|triggers| B["Span B"]
|
||||
end
|
||||
|
||||
subgraph links["Span Links"]
|
||||
direction TB
|
||||
X["Span X\n(Trace 1)"] -.-|link| Y["Span Y\n(Trace 2)"]
|
||||
end
|
||||
|
||||
parent_child ~~~ follows_from ~~~ links
|
||||
|
||||
style P fill:#0d47a1,stroke:#082f6a,color:#ffffff
|
||||
style C fill:#1b5e20,stroke:#0d3d14,color:#ffffff
|
||||
style A fill:#0d47a1,stroke:#082f6a,color:#ffffff
|
||||
style B fill:#bf360c,stroke:#8c2809,color:#ffffff
|
||||
style X fill:#4a148c,stroke:#38006b,color:#ffffff
|
||||
style Y fill:#4a148c,stroke:#38006b,color:#ffffff
|
||||
```
|
||||
|
||||
| Relationship | Same Trace? | Dependency? | OTel Mechanism |
|
||||
| ---------------- | ----------- | -------------------------- | ----------------- |
|
||||
| **Parent-Child** | Yes | Parent depends on child | `parent_span_id` |
|
||||
| **Follows-From** | Usually | Causal but no dependency | Link + attributes |
|
||||
| **Span Link** | Either | Correlation, no dependency | Link + attributes |
|
||||
|
||||
---
|
||||
|
||||
## Trace ID Generation
|
||||
|
||||
A `trace_id` is a 128-bit (16-byte) identifier that groups all spans belonging to one logical operation. How it's generated determines how easily you can find and correlate traces later.
|
||||
|
||||
### General Approaches
|
||||
|
||||
#### 1. Random (W3C Default)
|
||||
|
||||
Generate a random 128-bit ID when a trace starts. Standard approach for most services.
|
||||
|
||||
```
|
||||
trace_id = random_128_bits()
|
||||
```
|
||||
|
||||
| Pros | Cons |
|
||||
| --------------------------- | --------------------------------------------- |
|
||||
| Simple, standard | No natural correlation to domain events |
|
||||
| Guaranteed unique per trace | If propagation is lost, trace is broken |
|
||||
| Works with all OTel tooling | "Find trace for TX abc" requires index lookup |
|
||||
|
||||
#### 2. Deterministic (Derived from Domain Data)
|
||||
|
||||
Compute the trace_id from a hash of a natural identifier. Every node independently derives the **same** trace_id for the same event.
|
||||
|
||||
```
|
||||
trace_id = SHA-256(domain_identifier)[0:16] // truncate to 128 bits
|
||||
```
|
||||
|
||||
| Pros | Cons |
|
||||
| --------------------------------------------------- | ---------------------------------------------------------- |
|
||||
| Propagation-resilient — same ID computed everywhere | Same event processed twice (retry) shares trace_id |
|
||||
| Natural search — domain ID maps directly to trace | Non-standard (tooling assumes random) |
|
||||
| No coordination needed between nodes | 256→128 bit truncation (collision risk negligible at ~2⁶⁴) |
|
||||
|
||||
#### 3. Hybrid (Deterministic Prefix + Random Suffix)
|
||||
|
||||
First 8 bytes derived from domain data, last 8 bytes random.
|
||||
|
||||
```
|
||||
trace_id = SHA-256(domain_identifier)[0:8] || random_64_bits()
|
||||
```
|
||||
|
||||
| Pros | Cons |
|
||||
| ------------------------------------------- | ---------------------------------------- |
|
||||
| Prefix search: "find all traces for TX abc" | Must propagate to maintain full trace_id |
|
||||
| Unique per processing instance | More complex generation logic |
|
||||
| Retries get distinct trace_ids | Partial correlation only (prefix match) |
|
||||
|
||||
### XRPL Workflow Analysis
|
||||
|
||||
XRPL has a unique advantage: its core workflows produce **globally unique 256-bit hashes** that are known on every node. This makes deterministic trace_id generation practical in ways most systems can't achieve.
|
||||
|
||||
#### Natural Identifiers by Workflow
|
||||
|
||||
| Workflow | Natural Identifier | Size | Known at Start? | Same on All Nodes? |
|
||||
| ------------------- | --------------------------------- | ---------- | ----------------------------- | -------------------------------- |
|
||||
| **Transaction** | Transaction hash (`tid_`) | 256-bit | Yes — computed before signing | Yes — hash of canonical tx data |
|
||||
| **Consensus round** | Previous ledger hash + ledger seq | 256+32 bit | Yes — known when round opens | Yes — all validators agree |
|
||||
| **Validation** | Ledger hash being validated | 256-bit | Yes — from consensus result | Yes — same closed ledger |
|
||||
| **Ledger catch-up** | Target ledger hash | 256-bit | Yes — we know what to fetch | Yes — identifies ledger globally |
|
||||
|
||||
#### Where These Identifiers Live in Code
|
||||
|
||||
```
|
||||
Transaction: STTx::getTransactionID() → uint256 tid_
|
||||
TMTransaction::rawTransaction → recompute hash from bytes
|
||||
|
||||
Consensus: ConsensusProposal::prevLedger_ → uint256 (previous ledger hash)
|
||||
ConsensusProposal::position_ → uint256 (TxSet hash)
|
||||
LedgerHeader::seq → uint32_t (ledger sequence)
|
||||
|
||||
Validation: STValidation::getLedgerHash() → uint256
|
||||
STValidation::getNodeID() → NodeID (160-bit)
|
||||
|
||||
Ledger fetch: InboundLedger constructor → uint256 hash, uint32_t seq
|
||||
TMGetLedger::ledgerHash → bytes (uint256)
|
||||
```
|
||||
|
||||
### Recommended Strategy: Workflow-Scoped Deterministic
|
||||
|
||||
Each workflow type derives its trace_id from its natural domain identifier:
|
||||
|
||||
```
|
||||
Transaction trace: trace_id = SHA-256("tx" || tx_hash)[0:16]
|
||||
Consensus trace: trace_id = SHA-256("cons" || prev_ledger_hash || ledger_seq)[0:16]
|
||||
Ledger catch-up: trace_id = SHA-256("fetch" || target_ledger_hash)[0:16]
|
||||
```
|
||||
|
||||
The string prefix (`"tx"`, `"cons"`, `"fetch"`) prevents collisions between workflows that might share underlying hashes.
|
||||
|
||||
**Why this works for XRPL:**
|
||||
|
||||
1. **Propagation-resilient** — Even if a P2P message drops trace context, every node independently computes the same trace_id from the same tx_hash or ledger_hash. Spans still correlate.
|
||||
|
||||
2. **Zero-cost search** — "Show me the trace for transaction ABC" becomes a direct lookup: compute `SHA-256("tx" || ABC)[0:16]` and query. No secondary index needed.
|
||||
|
||||
3. **Cross-workflow linking via Span Links** — A consensus trace links to individual transaction traces. A validation span links to the consensus trace. This connects the full picture without forcing everything into one giant trace.
|
||||
|
||||
### Cross-Workflow Correlation
|
||||
|
||||
Each workflow gets its own trace. Span Links tie them together:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph tx_trace["Transaction Trace"]
|
||||
direction LR
|
||||
Tn["trace_id = f(tx_hash)"]:::note --> T1["tx.receive"] --> T2["tx.validate"] --> T3["tx.relay"]
|
||||
end
|
||||
|
||||
subgraph cons_trace["Consensus Trace"]
|
||||
direction LR
|
||||
Cn["trace_id = f(prev_ledger, seq)"]:::note --> C1["cons.open"] --> C2["cons.propose"] --> C3["cons.accept"]
|
||||
end
|
||||
|
||||
subgraph val_trace["Validation"]
|
||||
direction LR
|
||||
Vn["spans within consensus trace"]:::note --> V1["val.create"] --> V2["val.broadcast"]
|
||||
end
|
||||
|
||||
subgraph fetch_trace["Catch-Up Trace"]
|
||||
direction LR
|
||||
Fn["trace_id = f(ledger_hash)"]:::note --> F1["fetch.request"] --> F2["fetch.receive"] --> F3["fetch.apply"]
|
||||
end
|
||||
|
||||
C1 -.-|"span link\n(tx traces)"| T3
|
||||
C3 --> V1
|
||||
F1 -.-|"span link\n(target ledger)"| C3
|
||||
|
||||
classDef note fill:none,stroke:#888,stroke-dasharray:5 5,color:#333,font-style:italic
|
||||
style T1 fill:#0d47a1,stroke:#082f6a,color:#ffffff
|
||||
style T2 fill:#0d47a1,stroke:#082f6a,color:#ffffff
|
||||
style T3 fill:#0d47a1,stroke:#082f6a,color:#ffffff
|
||||
style C1 fill:#1b5e20,stroke:#0d3d14,color:#ffffff
|
||||
style C2 fill:#1b5e20,stroke:#0d3d14,color:#ffffff
|
||||
style C3 fill:#1b5e20,stroke:#0d3d14,color:#ffffff
|
||||
style V1 fill:#bf360c,stroke:#8c2809,color:#ffffff
|
||||
style V2 fill:#bf360c,stroke:#8c2809,color:#ffffff
|
||||
style F1 fill:#4a148c,stroke:#38006b,color:#ffffff
|
||||
style F2 fill:#4a148c,stroke:#38006b,color:#ffffff
|
||||
style F3 fill:#4a148c,stroke:#38006b,color:#ffffff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **Transaction Trace (blue)**: An independent trace whose `trace_id` is deterministically derived from the transaction hash. Contains receive, validate, and relay spans.
|
||||
- **Consensus Trace (green)**: An independent trace whose `trace_id` is derived from the previous ledger hash and sequence number. Covers the open, propose, and accept phases.
|
||||
- **Validation (red)**: Validation spans live within the consensus trace (not a separate trace). They are created after the accept phase completes.
|
||||
- **Catch-Up Trace (purple)**: An independent trace for ledger acquisition, derived from the target ledger hash. Used when a node is behind and fetching missing ledgers.
|
||||
- **Dotted arrows (span links)**: Cross-trace correlations. Consensus links to transaction traces it included; catch-up links to the consensus trace that produced the target ledger.
|
||||
- **Solid arrow (C3 to V1)**: A parent-child relationship -- validation spans are direct children of the consensus accept span within the same trace.
|
||||
|
||||
**How a query flows:**
|
||||
|
||||
```
|
||||
"Why was TX abc slow?"
|
||||
1. Compute trace_id = SHA-256("tx" || abc)[0:16]
|
||||
2. Find transaction trace → see it was included in consensus round N
|
||||
3. Follow span link → consensus trace for round N
|
||||
4. See which phase was slow (propose? accept?)
|
||||
5. If a node was catching up, follow link → catch-up trace
|
||||
```
|
||||
|
||||
### Trade-offs to Consider
|
||||
|
||||
| Concern | Mitigation |
|
||||
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Retries get same trace_id** | Add `attempt` attribute to root span; spans have unique span_ids and timestamps |
|
||||
| **256→128 bit truncation** | Birthday-bound collision at ~2⁶⁴ operations — negligible for XRPL's throughput |
|
||||
| **Non-standard generation** | OTel spec allows any 16-byte non-zero value; tooling works on the hex string |
|
||||
| **Hash computation cost** | SHA-256 is ~0.3μs per call; XRPL already computes these hashes for other purposes |
|
||||
| **Late-binding identifiers** | Ledger hash isn't known until after consensus — validation spans use ledger_seq as fallback, then link to the consensus trace |
|
||||
|
||||
---
|
||||
|
||||
## Distributed Traces Across Nodes
|
||||
|
||||
In distributed systems like xrpld, traces span **multiple independent nodes**. The trace context must be propagated in network messages:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant NodeA as Node A
|
||||
participant NodeB as Node B
|
||||
participant NodeC as Node C
|
||||
|
||||
Client->>NodeA: Submit TX<br/>(no trace context)
|
||||
|
||||
Note over NodeA: Creates new trace<br/>trace_id: abc123<br/>span: tx.receive
|
||||
|
||||
NodeA->>NodeB: Relay TX<br/>(trace_id: abc123, parent: 001)
|
||||
|
||||
Note over NodeB: Creates child span<br/>span: tx.relay<br/>parent_span_id: 001
|
||||
|
||||
NodeA->>NodeC: Relay TX<br/>(trace_id: abc123, parent: 001)
|
||||
|
||||
Note over NodeC: Creates child span<br/>span: tx.relay<br/>parent_span_id: 001
|
||||
|
||||
Note over NodeA,NodeC: All spans share trace_id: abc123<br/>enabling correlation across nodes
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **Client**: The external entity that submits a transaction. It does not carry trace context -- the trace originates at the first node.
|
||||
- **Node A**: The entry point that creates a new trace (trace_id: abc123) and the root span `tx.receive`. It relays the transaction to peers with trace context attached.
|
||||
- **Node B and Node C**: Peer nodes that receive the relayed transaction along with the propagated trace context. Each creates a child span under Node A's span, preserving the same `trace_id`.
|
||||
- **Arrows with trace context**: The relay messages carry `trace_id` and `parent_span_id`, allowing each downstream node to link its spans back to the originating span on Node A.
|
||||
|
||||
---
|
||||
|
||||
## Context Propagation
|
||||
|
||||
For traces to work across nodes, **trace context must be propagated** in messages.
|
||||
|
||||
### What's in the Context (~26 bytes)
|
||||
|
||||
| Field | Size | Description |
|
||||
| ------------- | -------- | ------------------------------------------------------- |
|
||||
| `trace_id` | 16 bytes | Identifies the entire trace (constant across all nodes) |
|
||||
| `span_id` | 8 bytes | The sender's current span (becomes parent on receiver) |
|
||||
| `trace_flags` | 1 byte | Sampling decision (bit 0 = sampled; bits 1-7 reserved) |
|
||||
| `trace_state` | variable | Optional vendor-specific data (typically omitted) |
|
||||
|
||||
### How span_id Changes at Each Hop
|
||||
|
||||
Only **one** `span_id` travels in the context - the sender's current span. Each node:
|
||||
|
||||
1. Extracts the received `span_id` and uses it as the `parent_span_id`
|
||||
2. Creates a **new** `span_id` for its own span
|
||||
3. Sends its own `span_id` as the parent when forwarding
|
||||
|
||||
```
|
||||
Node A Node B Node C
|
||||
────── ────── ──────
|
||||
|
||||
Span AAA Span BBB Span CCC
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
Context out: Context out: Context out:
|
||||
├─ trace_id: abc123 ├─ trace_id: abc123 ├─ trace_id: abc123
|
||||
├─ span_id: AAA ──────────► ├─ span_id: BBB ──────────► ├─ span_id: CCC ──────►
|
||||
└─ flags: 01 └─ flags: 01 └─ flags: 01
|
||||
│ │
|
||||
parent = AAA parent = BBB
|
||||
```
|
||||
|
||||
The `trace_id` stays constant, but `span_id` **changes at every hop** to maintain the parent-child chain.
|
||||
|
||||
### Propagation Formats
|
||||
|
||||
There are two patterns:
|
||||
|
||||
### HTTP/RPC Headers (W3C Trace Context)
|
||||
|
||||
```
|
||||
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
|
||||
│ │ │ │
|
||||
│ │ │ └── Flags (sampled)
|
||||
│ │ └── Parent span ID (16 hex)
|
||||
│ └── Trace ID (32 hex)
|
||||
└── Version
|
||||
```
|
||||
|
||||
### Protocol Buffers (xrpld P2P messages)
|
||||
|
||||
xrpld P2P messages such as `TMTransaction` carry the trace context in two added byte fields alongside the existing payload: `trace_parent` holds the W3C traceparent (`trace_id`, `span_id`, and `trace_flags`), and `trace_state` holds the optional W3C tracestate. Together they propagate the trace across the P2P boundary so a receiving node can attach its spans to the sender's span.
|
||||
|
||||
---
|
||||
|
||||
## Sampling
|
||||
|
||||
Not every trace needs to be recorded. **Sampling** reduces overhead:
|
||||
|
||||
### Head Sampling (at trace start)
|
||||
|
||||
```
|
||||
Request arrives → Random N% chance → Record or skip entire trace
|
||||
```
|
||||
|
||||
- ✅ Low overhead
|
||||
- ❌ May miss interesting traces
|
||||
|
||||
> **xrpld note**: xrpld intentionally fixes head sampling at 100% (sample
|
||||
> everything) and does not expose a configurable ratio. A per-node ratio
|
||||
> would let different nodes make divergent keep/drop decisions for the same
|
||||
> distributed trace, producing broken/partial traces. xrpld uses a
|
||||
> `ParentBased` sampler so spans with a remote parent honor the upstream
|
||||
> decision. Volume reduction is delegated to collector-side tail sampling.
|
||||
|
||||
### Tail Sampling (after trace completes)
|
||||
|
||||
```
|
||||
Trace completes → Collector evaluates:
|
||||
- Error? → KEEP
|
||||
- Slow? → KEEP
|
||||
- Normal? → Sample 10%
|
||||
```
|
||||
|
||||
- ✅ Never loses important traces
|
||||
- ❌ Higher memory usage at collector
|
||||
|
||||
---
|
||||
|
||||
## Key Benefits for xrpld
|
||||
|
||||
| Challenge | How Tracing Helps |
|
||||
| ---------------------------------- | ---------------------------------------- |
|
||||
| "Where is my transaction?" | Follow trace across all nodes it touched |
|
||||
| "Why was consensus slow?" | See timing breakdown of each phase |
|
||||
| "Which node is the bottleneck?" | Compare span durations across nodes |
|
||||
| "What happened during the outage?" | Correlate errors across the network |
|
||||
|
||||
---
|
||||
|
||||
## Glossary
|
||||
|
||||
| Term | Definition |
|
||||
| -------------------- | ------------------------------------------------------------------- |
|
||||
| **Trace** | Complete journey of a request, identified by `trace_id` |
|
||||
| **Span** | Single operation within a trace |
|
||||
| **Parent-Child** | Span relationship where the parent depends on the child |
|
||||
| **Follows-From** | Causal relationship where originator doesn't wait for the result |
|
||||
| **Span Link** | Non-hierarchical connection between spans, possibly across traces |
|
||||
| **Deterministic ID** | Trace ID derived from domain data (e.g., tx_hash) instead of random |
|
||||
| **Context** | Data propagated between services (`trace_id`, `span_id`, flags) |
|
||||
| **Instrumentation** | Code that creates spans and propagates context |
|
||||
| **Collector** | Service that receives, processes, and exports traces |
|
||||
| **Backend** | Storage/visualization system (Tempo) |
|
||||
| **Head Sampling** | Sampling decision at trace start |
|
||||
| **Tail Sampling** | Sampling decision after trace completes |
|
||||
|
||||
---
|
||||
|
||||
_Next: [Architecture Analysis](./01-architecture-analysis.md)_ | _Back to: [Overview](./OpenTelemetryPlan.md)_
|
||||
467
OpenTelemetryPlan/01-architecture-analysis.md
Normal file
467
OpenTelemetryPlan/01-architecture-analysis.md
Normal file
@@ -0,0 +1,467 @@
|
||||
# Architecture Analysis
|
||||
|
||||
> **Parent Document**: [OpenTelemetryPlan.md](./OpenTelemetryPlan.md)
|
||||
> **Related**: [Design Decisions](./02-design-decisions.md) | [Implementation Strategy](./03-implementation-strategy.md)
|
||||
|
||||
---
|
||||
|
||||
## 1.1 Current xrpld Architecture Overview
|
||||
|
||||
> **WS** = WebSocket | **UNL** = Unique Node List | **TxQ** = Transaction Queue | **StatsD** = Statistics Daemon
|
||||
|
||||
The xrpld node software consists of several interconnected components that need instrumentation for distributed tracing:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph xrpld["xrpld Node"]
|
||||
subgraph services["Core Services"]
|
||||
RPC["RPC Server<br/>(HTTP/WS/gRPC)"]
|
||||
Overlay["Overlay<br/>(P2P Network)"]
|
||||
Consensus["Consensus<br/>(RCLConsensus)"]
|
||||
ValidatorList["ValidatorList<br/>(UNL Mgmt)"]
|
||||
end
|
||||
|
||||
JobQueue["JobQueue<br/>(Thread Pool)"]
|
||||
|
||||
subgraph processing["Processing Layer"]
|
||||
NetworkOPs["NetworkOPs<br/>(Tx Processing)"]
|
||||
LedgerMaster["LedgerMaster<br/>(Ledger Mgmt)"]
|
||||
NodeStore["NodeStore<br/>(Database)"]
|
||||
InboundLedgers["InboundLedgers<br/>(Ledger Sync)"]
|
||||
end
|
||||
|
||||
subgraph appservices["Application Services"]
|
||||
PathFind["PathFinding<br/>(Payment Paths)"]
|
||||
TxQ["TxQ<br/>(Fee Escalation)"]
|
||||
LoadMgr["LoadManager<br/>(Fee/Load)"]
|
||||
end
|
||||
|
||||
subgraph observability["Existing Observability"]
|
||||
PerfLog["PerfLog<br/>(JSON)"]
|
||||
Insight["Insight<br/>(StatsD)"]
|
||||
Logging["Logging<br/>(Journal)"]
|
||||
end
|
||||
|
||||
services --> JobQueue
|
||||
JobQueue --> processing
|
||||
JobQueue --> appservices
|
||||
end
|
||||
|
||||
style xrpld fill:#424242,stroke:#212121,color:#ffffff
|
||||
style services fill:#1565c0,stroke:#0d47a1,color:#ffffff
|
||||
style processing fill:#2e7d32,stroke:#1b5e20,color:#ffffff
|
||||
style appservices fill:#6a1b9a,stroke:#4a148c,color:#ffffff
|
||||
style observability fill:#e65100,stroke:#bf360c,color:#ffffff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **Core Services (blue)**: The entry points into xrpld -- RPC Server handles client requests, Overlay manages peer-to-peer networking, Consensus drives agreement, and ValidatorList manages trusted validators.
|
||||
- **JobQueue (center)**: The asynchronous thread pool that decouples Core Services from the Processing and Application layers. All work flows through it.
|
||||
- **Processing Layer (green)**: Core business logic -- NetworkOPs processes transactions, LedgerMaster manages ledger state, NodeStore handles persistence, and InboundLedgers synchronizes missing data.
|
||||
- **Application Services (purple)**: Higher-level features -- PathFinding computes payment routes, TxQ manages fee-based queuing, and LoadManager tracks server load.
|
||||
- **Existing Observability (orange)**: The current monitoring stack (PerfLog, Insight, Journal logging) that OpenTelemetry will complement, not replace.
|
||||
- **Arrows (Services to JobQueue to layers)**: Work originates at Core Services, is enqueued onto the JobQueue, and dispatched to Processing or Application layers for execution.
|
||||
|
||||
---
|
||||
|
||||
## 1.1.1 Actors and Actions
|
||||
|
||||
### Actors
|
||||
|
||||
| Who (Plain English) | Technical Term |
|
||||
| ----------------------------------------- | -------------------------- |
|
||||
| Network node running XRPL software | xrpld node |
|
||||
| External client submitting requests | RPC Client |
|
||||
| Network neighbor sharing data | Peer (PeerImp) |
|
||||
| Request handler for client queries | RPC Server (ServerHandler) |
|
||||
| Command executor for specific RPC methods | RPCHandler |
|
||||
| Agreement process between nodes | Consensus (RCLConsensus) |
|
||||
| Transaction processing coordinator | NetworkOPs |
|
||||
| Background task scheduler | JobQueue |
|
||||
| Ledger state manager | LedgerMaster |
|
||||
| Payment route calculator | PathFinding (Pathfinder) |
|
||||
| Transaction waiting room | TxQ (Transaction Queue) |
|
||||
| Fee adjustment system | LoadManager |
|
||||
| Trusted validator list manager | ValidatorList |
|
||||
| Protocol upgrade tracker | AmendmentTable |
|
||||
| Ledger state hash tree | SHAMap |
|
||||
| Persistent key-value storage | NodeStore |
|
||||
|
||||
### Actions
|
||||
|
||||
| What Happens (Plain English) | Technical Term |
|
||||
| ---------------------------------------------- | ---------------------- |
|
||||
| Client sends a request to a node | `rpc.request` |
|
||||
| Node executes a specific RPC command | `rpc.command.*` |
|
||||
| Node receives a transaction from a peer | `tx.receive` |
|
||||
| Node checks if a transaction is valid | `tx.validate` |
|
||||
| Node forwards a transaction to neighbors | `tx.relay` |
|
||||
| Nodes agree on which transactions to include | `consensus.round` |
|
||||
| Consensus progresses through phases | `consensus.phase.*` |
|
||||
| Node builds a new confirmed ledger | `ledger.build` |
|
||||
| Node fetches missing ledger data from peers | `ledger.acquire` |
|
||||
| Node computes payment routes | `pathfind.compute` |
|
||||
| Node queues a transaction for later processing | `txq.enqueue` |
|
||||
| Node increases fees due to high load | `fee.escalate` |
|
||||
| Node fetches the latest trusted validator list | `validator.list.fetch` |
|
||||
| Node votes on a protocol amendment | `amendment.vote` |
|
||||
| Node synchronizes state tree data | `shamap.sync` |
|
||||
|
||||
---
|
||||
|
||||
## 1.2 Key Components for Instrumentation
|
||||
|
||||
> **TxQ** = Transaction Queue | **UNL** = Unique Node List
|
||||
|
||||
| Component | Location | Purpose | Trace Value |
|
||||
| ------------------ | ------------------------------------------ | ------------------------ | -------------------------------- |
|
||||
| **Overlay** | `src/xrpld/overlay/` | P2P communication | Message propagation timing |
|
||||
| **PeerImp** | `src/xrpld/overlay/detail/PeerImp.cpp` | Individual peer handling | Per-peer latency |
|
||||
| **RCLConsensus** | `src/xrpld/app/consensus/RCLConsensus.cpp` | Consensus algorithm | Round timing, phase analysis |
|
||||
| **NetworkOPs** | `src/xrpld/app/misc/NetworkOPs.cpp` | Transaction processing | Tx lifecycle tracking |
|
||||
| **ServerHandler** | `src/xrpld/rpc/detail/ServerHandler.cpp` | RPC entry point | Request latency |
|
||||
| **RPCHandler** | `src/xrpld/rpc/detail/RPCHandler.cpp` | Command execution | Per-command timing |
|
||||
| **JobQueue** | `src/xrpl/core/JobQueue.h` | Async task execution | Queue wait times |
|
||||
| **PathFinding** | `src/xrpld/app/paths/` | Payment path computation | Path latency, cache hits |
|
||||
| **TxQ** | `src/xrpld/app/misc/TxQ.cpp` | Transaction queue/fees | Queue depth, eviction rates |
|
||||
| **LoadManager** | `src/xrpld/app/main/LoadManager.cpp` | Fee escalation/load | Fee levels, load factors |
|
||||
| **InboundLedgers** | `src/xrpld/app/ledger/InboundLedgers.cpp` | Ledger acquisition | Sync time, peer reliability |
|
||||
| **ValidatorList** | `src/xrpld/app/misc/ValidatorList.cpp` | UNL management | List freshness, fetch failures |
|
||||
| **AmendmentTable** | `src/xrpld/app/misc/AmendmentTable.cpp` | Protocol amendments | Voting status, activation events |
|
||||
| **SHAMap** | `src/xrpld/shamap/` | State hash tree | Sync speed, missing nodes |
|
||||
|
||||
---
|
||||
|
||||
## 1.3 Transaction Flow Diagram
|
||||
|
||||
Transaction flow spans multiple nodes in the network. Each node creates linked spans to form a distributed trace:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant PeerA as Peer A (Receive)
|
||||
participant PeerB as Peer B (Relay)
|
||||
participant PeerC as Peer C (Validate)
|
||||
|
||||
Client->>PeerA: 1. Submit TX
|
||||
|
||||
rect rgb(230, 245, 255)
|
||||
Note over PeerA: tx.receive SPAN START
|
||||
PeerA->>PeerA: HashRouter Deduplication
|
||||
PeerA->>PeerA: tx.validate (child span)
|
||||
end
|
||||
|
||||
PeerA->>PeerB: 2. Relay TX (with trace ctx)
|
||||
|
||||
rect rgb(230, 245, 255)
|
||||
Note over PeerB: tx.receive (linked span)
|
||||
end
|
||||
|
||||
PeerB->>PeerC: 3. Relay TX
|
||||
|
||||
rect rgb(230, 245, 255)
|
||||
Note over PeerC: tx.receive (linked span)
|
||||
PeerC->>PeerC: tx.process
|
||||
end
|
||||
|
||||
Note over Client,PeerC: DISTRIBUTED TRACE (same trace_id: abc123)
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **Client**: The external entity that submits a transaction to Peer A. It has no trace context -- the trace starts at the first node.
|
||||
- **Peer A (Receive)**: The entry node that creates the root span `tx.receive`, runs HashRouter deduplication to avoid processing duplicates, and creates a child `tx.validate` span.
|
||||
- **Peer A to Peer B arrow**: The relay message carries trace context (trace_id + parent span_id), enabling Peer B to create a linked span under the same trace.
|
||||
- **Peer B (Relay)**: Receives the transaction and trace context, creates a `tx.receive` span linked to Peer A's trace, then relays onward.
|
||||
- **Peer C (Validate)**: Final hop in this example. Creates a linked `tx.receive` span and runs `tx.process` to fully process the transaction.
|
||||
- **Blue rectangles**: Highlight the span boundaries on each node, showing where instrumentation creates and closes spans.
|
||||
|
||||
### Trace Structure
|
||||
|
||||
```
|
||||
trace_id: abc123
|
||||
├── span: tx.receive (Peer A)
|
||||
│ ├── span: tx.validate
|
||||
│ └── span: tx.relay
|
||||
├── span: tx.receive (Peer B) [parent: Peer A]
|
||||
│ └── span: tx.relay
|
||||
└── span: tx.receive (Peer C) [parent: Peer B]
|
||||
└── span: tx.process
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1.4 Consensus Round Flow
|
||||
|
||||
Consensus rounds are multi-phase operations that benefit significantly from tracing:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph round["consensus.round (root span)"]
|
||||
attrs["Attributes:<br/>ledger_seq = 12345678<br/>consensus_mode = proposing<br/>proposers = 35"]
|
||||
|
||||
subgraph open["consensus.phase.open"]
|
||||
open_desc["Duration: ~3s<br/>Waiting for transactions"]
|
||||
end
|
||||
|
||||
subgraph establish["consensus.phase.establish"]
|
||||
est_attrs["proposals_received = 28<br/>disputes_resolved = 3"]
|
||||
est_children["├── consensus.proposal.receive (×28)<br/>├── consensus.proposal.send (×1)<br/>└── consensus.dispute.resolve (×3)"]
|
||||
end
|
||||
|
||||
subgraph accept["consensus.phase.accept"]
|
||||
acc_attrs["transactions_applied = 150<br/>ledger_hash = DEF456..."]
|
||||
acc_children["├── ledger.build<br/>└── ledger.validate"]
|
||||
end
|
||||
|
||||
attrs --> open
|
||||
open --> establish
|
||||
establish --> accept
|
||||
end
|
||||
|
||||
style round fill:#f57f17,stroke:#e65100,color:#ffffff
|
||||
style open fill:#1565c0,stroke:#0d47a1,color:#ffffff
|
||||
style establish fill:#2e7d32,stroke:#1b5e20,color:#ffffff
|
||||
style accept fill:#c2185b,stroke:#880e4f,color:#ffffff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **consensus.round (orange, root span)**: The top-level span encompassing the entire consensus round, with attributes like ledger sequence, mode, and proposer count.
|
||||
- **consensus.phase.open (blue)**: The first phase where the node waits (~3s) to collect incoming transactions before proposing.
|
||||
- **consensus.phase.establish (green)**: The negotiation phase where validators exchange proposals, resolve disputes, and converge on a transaction set. Child spans track each proposal received/sent and each dispute resolved.
|
||||
- **consensus.phase.accept (pink)**: The final phase where the agreed transaction set is applied, a new ledger is built, and the ledger is validated. Child spans cover `ledger.build` and `ledger.validate`.
|
||||
- **Arrows (open to establish to accept)**: The sequential flow through the three consensus phases. Each phase must complete before the next begins.
|
||||
|
||||
---
|
||||
|
||||
## 1.5 RPC Request Flow
|
||||
|
||||
> **WS** = WebSocket
|
||||
|
||||
RPC requests support W3C Trace Context headers for distributed tracing across services:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph request["rpc.request (root span)"]
|
||||
http["HTTP Request — POST /<br/>traceparent:<br/>00-abc123...-def456...-01"]
|
||||
|
||||
attrs["Attributes:<br/>http.method = POST<br/>net.peer.ip = 192.168.1.100<br/>command = submit"]
|
||||
|
||||
subgraph enqueue["jobqueue.enqueue"]
|
||||
job_attr["job_type = jtCLIENT_RPC"]
|
||||
end
|
||||
|
||||
subgraph command["rpc.command.submit"]
|
||||
cmd_attrs["version = 2<br/>rpc_role = user"]
|
||||
cmd_children["├── tx.deserialize<br/>├── tx.validate_local<br/>└── tx.submit_to_network"]
|
||||
end
|
||||
|
||||
response["Response: 200 OK<br/>Duration: 45ms"]
|
||||
|
||||
http --> attrs
|
||||
attrs --> enqueue
|
||||
enqueue --> command
|
||||
command --> response
|
||||
end
|
||||
|
||||
style request fill:#2e7d32,stroke:#1b5e20,color:#ffffff
|
||||
style enqueue fill:#1565c0,stroke:#0d47a1,color:#ffffff
|
||||
style command fill:#e65100,stroke:#bf360c,color:#ffffff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **rpc.request (green, root span)**: The outermost span representing the full RPC request lifecycle, from HTTP receipt to response. Carries the W3C `traceparent` header for distributed tracing.
|
||||
- **HTTP Request node**: Shows the incoming POST request with its `traceparent` header and extracted attributes (method, peer IP, command name).
|
||||
- **jobqueue.enqueue (blue)**: The span covering the asynchronous handoff from the RPC thread to the JobQueue worker thread. The trace context is preserved across this async boundary.
|
||||
- **rpc.command.submit (orange)**: The span for the actual command execution, with child spans for deserialization, local validation, and network submission.
|
||||
- **Response node**: The final output with HTTP status and total duration, marking the end of the root span.
|
||||
- **Arrows (top to bottom)**: The sequential processing pipeline -- receive request, extract attributes, enqueue job, execute command, return response.
|
||||
|
||||
---
|
||||
|
||||
## 1.6 Key Trace Points
|
||||
|
||||
> **TxQ** = Transaction Queue
|
||||
|
||||
The following table identifies priority instrumentation points across the codebase:
|
||||
|
||||
| Category | Span Name | File | Method | Priority |
|
||||
| --------------- | ---------------------- | ---------------------- | ----------------------- | -------- |
|
||||
| **Transaction** | `tx.receive` | `PeerImp.cpp` | `handleTransaction()` | High |
|
||||
| **Transaction** | `tx.validate` | `NetworkOPs.cpp` | `processTransaction()` | High |
|
||||
| **Transaction** | `tx.process` | `NetworkOPs.cpp` | `doTransactionSync()` | High |
|
||||
| **Transaction** | `tx.relay` | `OverlayImpl.cpp` | `relay()` | Medium |
|
||||
| **Consensus** | `consensus.round` | `RCLConsensus.cpp` | `startRound()` | High |
|
||||
| **Consensus** | `consensus.phase.*` | `Consensus.h` | `timerEntry()` | High |
|
||||
| **Consensus** | `consensus.proposal.*` | `RCLConsensus.cpp` | `peerProposal()` | Medium |
|
||||
| **RPC** | `rpc.request` | `ServerHandler.cpp` | `onRequest()` | High |
|
||||
| **RPC** | `rpc.command.*` | `RPCHandler.cpp` | `doCommand()` | High |
|
||||
| **Peer** | `peer.connect` | `OverlayImpl.cpp` | `onHandoff()` | Low |
|
||||
| **Peer** | `peer.message.*` | `PeerImp.cpp` | `onMessage()` | Low |
|
||||
| **Ledger** | `ledger.acquire` | `InboundLedgers.cpp` | `acquire()` | Medium |
|
||||
| **Ledger** | `ledger.build` | `RCLConsensus.cpp` | `buildLCL()` | High |
|
||||
| **PathFinding** | `pathfind.request` | `PathRequest.cpp` | `doUpdate()` | High |
|
||||
| **PathFinding** | `pathfind.compute` | `Pathfinder.cpp` | `findPaths()` | High |
|
||||
| **TxQ** | `txq.enqueue` | `TxQ.cpp` | `apply()` | High |
|
||||
| **TxQ** | `txq.apply` | `TxQ.cpp` | `processClosedLedger()` | High |
|
||||
| **Fee** | `fee.escalate` | `LoadManager.cpp` | `raiseLocalFee()` | Medium |
|
||||
| **Ledger** | `ledger.replay` | `LedgerReplayer.h` | `replay()` | Medium |
|
||||
| **Ledger** | `ledger.delta` | `LedgerDeltaAcquire.h` | `processData()` | Medium |
|
||||
| **Validator** | `validator.list.fetch` | `ValidatorList.cpp` | `verify()` | Medium |
|
||||
| **Validator** | `validator.manifest` | `Manifest.cpp` | `applyManifest()` | Low |
|
||||
| **Amendment** | `amendment.vote` | `AmendmentTable.cpp` | `doVoting()` | Low |
|
||||
| **SHAMap** | `shamap.sync` | `SHAMap.cpp` | `fetchRoot()` | Medium |
|
||||
|
||||
---
|
||||
|
||||
## 1.7 Instrumentation Priority
|
||||
|
||||
> **TxQ** = Transaction Queue
|
||||
|
||||
```mermaid
|
||||
quadrantChart
|
||||
title Instrumentation Priority Matrix
|
||||
x-axis Low Complexity --> High Complexity
|
||||
y-axis Low Value --> High Value
|
||||
quadrant-1 Implement First
|
||||
quadrant-2 Plan Carefully
|
||||
quadrant-3 Quick Wins
|
||||
quadrant-4 Consider Later
|
||||
|
||||
RPC Tracing: [0.2, 0.92]
|
||||
Transaction Tracing: [0.55, 0.88]
|
||||
Consensus Tracing: [0.78, 0.82]
|
||||
PathFinding: [0.38, 0.75]
|
||||
TxQ and Fees: [0.25, 0.65]
|
||||
Ledger Sync: [0.62, 0.58]
|
||||
Peer Message Tracing: [0.35, 0.25]
|
||||
JobQueue Tracing: [0.2, 0.48]
|
||||
Validator Mgmt: [0.48, 0.42]
|
||||
Amendment Tracking: [0.15, 0.32]
|
||||
SHAMap Operations: [0.72, 0.45]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1.8 Observable Outcomes
|
||||
|
||||
> **TxQ** = Transaction Queue | **UNL** = Unique Node List
|
||||
|
||||
After implementing OpenTelemetry, operators and developers will gain visibility into the following:
|
||||
|
||||
### 1.8.1 What You Will See: Traces
|
||||
|
||||
| Trace Type | Description | Example Query in Grafana/Tempo |
|
||||
| -------------------------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------- |
|
||||
| **Transaction Lifecycle** | Full journey from RPC submission through validation, relay, consensus, and ledger inclusion | `{service.name="xrpld" && tx_hash="ABC123..."}` |
|
||||
| **Cross-Node Propagation** | Transaction path across multiple xrpld nodes with timing | `{relay_count > 0}` |
|
||||
| **Consensus Rounds** | Complete round with all phases (open, establish, accept) | `{span.name=~"consensus.round.*"}` |
|
||||
| **RPC Request Processing** | Individual command execution with timing breakdown | `{command="account_info"}` |
|
||||
| **Ledger Acquisition** | Peer-to-peer ledger data requests and responses | `{span.name="ledger.acquire"}` |
|
||||
| **PathFinding Latency** | Path computation time and cache effectiveness for payment RPCs | `{span.name="pathfind.compute"}` |
|
||||
| **TxQ Behavior** | Queue depth, eviction patterns, fee escalation during congestion | `{span.name=~"txq.*"}` |
|
||||
| **Ledger Sync** | Full acquisition timeline including delta and transaction fetches | `{span.name=~"ledger.acquire.*"}` |
|
||||
| **Validator Health** | UNL fetch success, manifest updates, stale list detection | `{span.name=~"validator.*"}` |
|
||||
|
||||
### 1.8.2 What You Will See: Metrics (Derived from Traces)
|
||||
|
||||
| Metric | Description | Dashboard Panel |
|
||||
| ----------------------------- | --------------------------------------- | --------------------------- |
|
||||
| **RPC Latency (p50/p95/p99)** | Response time distribution per command | Heatmap by command |
|
||||
| **Transaction Throughput** | Transactions processed per second | Time series graph |
|
||||
| **Consensus Round Duration** | Time to complete consensus phases | Histogram |
|
||||
| **Cross-Node Latency** | Time for transaction to reach N nodes | Line chart with percentiles |
|
||||
| **Error Rate** | Failed transactions/RPC calls by type | Stacked bar chart |
|
||||
| **PathFinding Latency** | Path computation time per currency pair | Heatmap by currency |
|
||||
| **TxQ Depth** | Queued transactions over time | Time series with thresholds |
|
||||
| **Fee Escalation Level** | Current fee multiplier | Gauge with alert thresholds |
|
||||
| **Ledger Sync Duration** | Time to acquire missing ledgers | Histogram |
|
||||
|
||||
### 1.8.3 Concrete Dashboard Examples
|
||||
|
||||
**Transaction Trace View (Tempo):**
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Trace: abc123... (Transaction Submission) Duration: 847ms │
|
||||
├────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ├── rpc.request [ServerHandler] ████░░░░░░ 45ms │
|
||||
│ │ └── rpc.command.submit [RPCHandler] ████░░░░░░ 42ms │
|
||||
│ │ └── tx.receive [NetworkOPs] ███░░░░░░░ 35ms │
|
||||
│ │ ├── tx.validate [TxQ] █░░░░░░░░░ 8ms │
|
||||
│ │ └── tx.relay [Overlay] ██░░░░░░░░ 15ms │
|
||||
│ │ ├── tx.receive [Node-B] █████░░░░░ 52ms │
|
||||
│ │ │ └── tx.relay [Node-B] ██░░░░░░░░ 18ms │
|
||||
│ │ └── tx.receive [Node-C] ██████░░░░ 65ms │
|
||||
│ └── consensus.round [RCLConsensus] ████████░░ 720ms │
|
||||
│ ├── consensus.phase.open ██░░░░░░░░ 180ms │
|
||||
│ ├── consensus.phase.establish █████░░░░░ 480ms │
|
||||
│ └── consensus.phase.accept █░░░░░░░░░ 60ms │
|
||||
└────────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**RPC Performance Dashboard Panel:**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ RPC Command Latency (Last 1 Hour) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Command │ p50 │ p95 │ p99 │ Errors │ Rate │
|
||||
│──────────────────┼────────┼────────┼────────┼────────┼──────│
|
||||
│ account_info │ 12ms │ 45ms │ 89ms │ 0.1% │ 150/s│
|
||||
│ submit │ 35ms │ 120ms │ 250ms │ 2.3% │ 45/s│
|
||||
│ ledger │ 8ms │ 25ms │ 55ms │ 0.0% │ 80/s│
|
||||
│ tx │ 15ms │ 50ms │ 100ms │ 0.5% │ 60/s│
|
||||
│ server_info │ 5ms │ 12ms │ 20ms │ 0.0% │ 200/s│
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Consensus Health Dashboard Panel:**
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
xyChart:
|
||||
width: 1200
|
||||
height: 400
|
||||
plotReservedSpacePercent: 50
|
||||
chartOrientation: vertical
|
||||
themeVariables:
|
||||
xyChart:
|
||||
plotColorPalette: "#3498db"
|
||||
---
|
||||
xychart-beta
|
||||
title "Consensus Round Duration (Last 24 Hours)"
|
||||
x-axis "Time of Day (Hours)" [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24]
|
||||
y-axis "Duration (seconds)" 1 --> 5
|
||||
line [2.1, 2.4, 2.8, 3.2, 3.8, 4.3, 4.5, 5.0, 4.7, 4.0, 3.2, 2.6, 2.0]
|
||||
```
|
||||
|
||||
### 1.8.4 Operator Actionable Insights
|
||||
|
||||
| Scenario | What You'll See | Action |
|
||||
| ------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------ |
|
||||
| **Slow RPC** | Span showing which phase is slow (parsing, execution, serialization) | Optimize specific code path |
|
||||
| **Transaction Stuck** | Trace stops at validation; error attribute shows reason | Fix transaction parameters |
|
||||
| **Consensus Delay** | Phase.establish taking too long; proposer attribute shows missing validators | Investigate network connectivity |
|
||||
| **Memory Spike** | Large batch of spans correlating with memory increase | Tune batch_size or sampling |
|
||||
| **Network Partition** | Traces missing cross-node links for specific peer | Check peer connectivity |
|
||||
| **Path Computation Slow** | pathfind.compute span shows high latency; cache miss rate in attributes | Warm the RippleLineCache, check order book depth |
|
||||
| **TxQ Full** | txq.enqueue spans show evictions; fee.escalate spans increasing | Monitor fee levels, alert operators |
|
||||
| **Ledger Sync Stalled** | ledger.acquire spans timing out; peer reliability attributes show issues | Check peer connectivity, add trusted peers |
|
||||
| **UNL Stale** | validator.list.fetch spans failing; last_update attribute aging | Verify validator site URLs, check DNS |
|
||||
|
||||
### 1.8.5 Developer Debugging Workflow
|
||||
|
||||
1. **Find Transaction**: Query by `tx_hash` to get full trace
|
||||
2. **Identify Bottleneck**: Look at span durations to find slowest component
|
||||
3. **Check Attributes**: Review `validity`, `rpc_status` for errors
|
||||
4. **Correlate Logs**: Use `trace_id` to find related PerfLog entries
|
||||
5. **Compare Nodes**: Filter by `service.instance.id` to compare behavior across nodes
|
||||
|
||||
---
|
||||
|
||||
_Next: [Design Decisions](./02-design-decisions.md)_ | _Back to: [Overview](./OpenTelemetryPlan.md)_
|
||||
666
OpenTelemetryPlan/02-design-decisions.md
Normal file
666
OpenTelemetryPlan/02-design-decisions.md
Normal file
@@ -0,0 +1,666 @@
|
||||
# Design Decisions
|
||||
|
||||
> **Parent Document**: [OpenTelemetryPlan.md](./OpenTelemetryPlan.md)
|
||||
> **Related**: [Architecture Analysis](./01-architecture-analysis.md)
|
||||
|
||||
---
|
||||
|
||||
## 2.1 OpenTelemetry Components
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
### 2.1.1 SDK Selection
|
||||
|
||||
**Primary Choice**: OpenTelemetry C++ SDK (`opentelemetry-cpp`)
|
||||
|
||||
| Component | Purpose | Required |
|
||||
| --------------------------------------- | ---------------------- | ------------------------- |
|
||||
| `opentelemetry-cpp::api` | Tracing API headers | Yes |
|
||||
| `opentelemetry-cpp::sdk` | SDK implementation | Yes |
|
||||
| `opentelemetry-cpp::ext` | Extensions (exporters) | Yes |
|
||||
| `opentelemetry-cpp::otlp_http_exporter` | OTLP/HTTP export | Yes (shipped in Phase 1b) |
|
||||
| `opentelemetry-cpp::otlp_grpc_exporter` | OTLP/gRPC export | Future (not yet wired up) |
|
||||
|
||||
### 2.1.2 Instrumentation Strategy
|
||||
|
||||
**Manual Instrumentation** (recommended):
|
||||
|
||||
| Approach | Pros | Cons |
|
||||
| ---------- | --------------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| **Manual** | Precise control, optimized placement, xrpld-specific attributes | More development effort |
|
||||
| **Auto** | Less code, automatic coverage | Less control, potential overhead, limited customization |
|
||||
|
||||
---
|
||||
|
||||
## 2.2 Exporter Configuration
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph nodes["xrpld Nodes"]
|
||||
node1["xrpld<br/>Node 1"]
|
||||
node2["xrpld<br/>Node 2"]
|
||||
node3["xrpld<br/>Node 3"]
|
||||
end
|
||||
|
||||
collector["OpenTelemetry<br/>Collector<br/>(sidecar or standalone)"]
|
||||
|
||||
subgraph backends["Observability Backends"]
|
||||
tempo["Tempo"]
|
||||
elastic["Elastic<br/>APM"]
|
||||
end
|
||||
|
||||
node1 -->|"OTLP/HTTP<br/>:4318"| collector
|
||||
node2 -->|"OTLP/HTTP<br/>:4318"| collector
|
||||
node3 -->|"OTLP/HTTP<br/>:4318"| collector
|
||||
|
||||
collector --> tempo
|
||||
collector --> elastic
|
||||
|
||||
style nodes fill:#0d47a1,stroke:#082f6a,color:#ffffff
|
||||
style backends fill:#1b5e20,stroke:#0d3d14,color:#ffffff
|
||||
style collector fill:#bf360c,stroke:#8c2809,color:#ffffff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **xrpld Nodes (blue)**: The source of telemetry data. Each xrpld node exports spans via OTLP/HTTP on port 4318 (the only exporter shipped in Phase 1b).
|
||||
- **OpenTelemetry Collector (red)**: The central aggregation point that receives spans from all nodes. Can run as a sidecar (per-node) or standalone (shared). Handles batching, filtering, and routing.
|
||||
- **Observability Backends (green)**: The storage and visualization destinations. Tempo is the recommended backend for both development and production, and Elastic APM is an alternative. The Collector routes to one or more backends.
|
||||
- **Arrows (nodes to collector to backends)**: The data pipeline -- spans flow from nodes to the Collector over HTTP, then the Collector fans out to the configured backends.
|
||||
|
||||
### 2.2.1 OTLP/HTTP (Shipped in Phase 1b)
|
||||
|
||||
OTLP/HTTP is the only exporter wired up in Phase 1b. It is configured via
|
||||
`OtlpHttpExporterOptions` with the collector traces endpoint
|
||||
(`http://localhost:4318/v1/traces` by default) and a JSON content type
|
||||
(binary protobuf is also available).
|
||||
|
||||
### 2.2.2 OTLP/gRPC (Future Work — Planned Upgrade)
|
||||
|
||||
OTLP/gRPC is planned as a future upgrade from the HTTP exporter. The gRPC
|
||||
transport offers lower per-span overhead and tighter back-pressure semantics
|
||||
than HTTP/JSON, making it attractive for production deployments once the HTTP
|
||||
path is validated in earlier phases.
|
||||
|
||||
Required to land this upgrade:
|
||||
|
||||
1. Add `opentelemetry-cpp::otlp_grpc_exporter` to the Conan recipe (the
|
||||
dependency already exists but is not linked in Phase 1b builds).
|
||||
2. Extend `TelemetryConfig.cpp` to parse an `exporter` key (`otlp_http`
|
||||
default, `otlp_grpc` opt-in) and a gRPC endpoint override.
|
||||
3. In `Telemetry::start()` branch on the parsed exporter type and construct
|
||||
either `OtlpHttpExporterFactory::Create(httpOpts)` or
|
||||
`OtlpGrpcExporterFactory::Create(grpcOpts)` accordingly.
|
||||
4. Update the runbook and dashboards to document the alternate port and TLS
|
||||
settings.
|
||||
|
||||
When wired up, the gRPC path will use `OtlpGrpcExporterOptions` configured with
|
||||
the collector endpoint (host on port 4317), TLS credentials enabled, and a CA
|
||||
certificate path.
|
||||
|
||||
Until that work lands, `OtlpGrpcExporterOptions` is **not** used by any code
|
||||
path in Phase 1b through Phase 5.
|
||||
|
||||
---
|
||||
|
||||
## 2.3 Span Naming Conventions
|
||||
|
||||
> **TxQ** = Transaction Queue | **UNL** = Unique Node List | **WS** = WebSocket
|
||||
|
||||
### 2.3.1 Naming Schema
|
||||
|
||||
```
|
||||
<component>.<operation>[.<sub-operation>]
|
||||
```
|
||||
|
||||
**Examples**:
|
||||
|
||||
- `tx.receive` - Transaction received from peer
|
||||
- `consensus.phase.establish` - Consensus establish phase
|
||||
- `rpc.command.server_info` - server_info RPC command
|
||||
|
||||
### 2.3.2 Complete Span Catalog
|
||||
|
||||
| Span name | Description |
|
||||
| ------------------------------ | --------------------------------------- |
|
||||
| `tx.receive` | Transaction received from network |
|
||||
| `tx.validate` | Transaction signature/format validation |
|
||||
| `tx.process` | Full transaction processing |
|
||||
| `tx.relay` | Transaction relay to peers |
|
||||
| `tx.apply` | Apply transaction to ledger |
|
||||
| `consensus.round` | Complete consensus round |
|
||||
| `consensus.phase.open` | Open phase - collecting transactions |
|
||||
| `consensus.phase.establish` | Establish phase - reaching agreement |
|
||||
| `consensus.phase.accept` | Accept phase - applying consensus |
|
||||
| `consensus.proposal.receive` | Receive peer proposal |
|
||||
| `consensus.proposal.send` | Send our proposal |
|
||||
| `consensus.validation.receive` | Receive peer validation |
|
||||
| `consensus.validation.send` | Send our validation |
|
||||
| `rpc.request` | HTTP/WebSocket request handling |
|
||||
| `rpc.command.*` | Specific RPC command (dynamic) |
|
||||
| `peer.connect` | Peer connection establishment |
|
||||
| `peer.disconnect` | Peer disconnection |
|
||||
| `peer.message.send` | Send protocol message |
|
||||
| `peer.message.receive` | Receive protocol message |
|
||||
| `ledger.acquire` | Ledger acquisition from network |
|
||||
| `ledger.build` | Build new ledger |
|
||||
| `ledger.validate` | Ledger validation |
|
||||
| `ledger.close` | Close ledger |
|
||||
| `ledger.replay` | Ledger replay executed |
|
||||
| `ledger.delta` | Delta-based ledger acquired |
|
||||
| `pathfind.request` | Path request initiated |
|
||||
| `pathfind.compute` | Path computation executed |
|
||||
| `txq.enqueue` | Transaction queued |
|
||||
| `txq.apply` | Queued transaction applied |
|
||||
| `fee.escalate` | Fee escalation triggered |
|
||||
| `validator.list.fetch` | UNL list fetched |
|
||||
| `validator.manifest` | Manifest update processed |
|
||||
| `amendment.vote` | Amendment voting executed |
|
||||
| `shamap.sync` | State tree synchronization |
|
||||
| `job.enqueue` | Job added to queue |
|
||||
| `job.execute` | Job execution |
|
||||
|
||||
### 2.3.3 Attribute Naming Conventions
|
||||
|
||||
Span **names** follow §2.3.1 (dotted `<component>.<operation>`). Span
|
||||
**attribute keys** follow the rules below. The constants in the `*SpanNames.h`
|
||||
headers are the single source of truth; the collector, Tempo, the Grafana
|
||||
dashboards, and the runbook all consume these exact keys, so every layer must
|
||||
agree with the code. A CI check enforces this end to end.
|
||||
|
||||
1. **Per-span unique attribute** → bare field name, allowed when the field is
|
||||
recorded by a single span/workflow so the span name already supplies the
|
||||
domain (e.g. `command`, `version`, `local` on `rpc.command`).
|
||||
2. **Shared attribute (same concept on more than one span)** → ONE key, reused
|
||||
verbatim on every span that records it; the span name tells the occurrences
|
||||
apart, so no per-emitter prefix is added. Name it by the field's meaning: a
|
||||
property of a domain object keeps that object's bare field name (`ledger_hash`,
|
||||
`ledger_seq`, `tx_hash`, `peer_id`, `full_validation`); a field already
|
||||
qualified by a sub-kind keeps that qualifier on every emitter (`proposal_trusted`
|
||||
on both `consensus.proposal.receive` and `peer.proposal.receive`;
|
||||
`validation_trusted` likewise). Defined once in the base `SpanNames.h`
|
||||
`namespace attr` block and re-exported (`using`) by each domain header.
|
||||
3. **Collision qualifier** → `<domain>_<field>`, only when a bare name would
|
||||
collide with a DIFFERENT concept in the shared spanmetrics label space or with
|
||||
the OTel-reserved `status` key (e.g. `rpc_status`, `grpc_status`,
|
||||
`consensus_state`, `consensus_round`, `consensus_mode`). This disambiguates
|
||||
distinct concepts that share a word; it is NOT used to tag the same concept
|
||||
with its emitting workflow — that is rule 2 (one shared name).
|
||||
4. **Resource attribute** → dotted `xrpl.<subsystem>.<field>`, reserved ONLY
|
||||
for process/network identity set once at startup (`xrpl.network.id`,
|
||||
`xrpl.network.type`). Span attributes are never dotted in the `xrpl.` form —
|
||||
it blurs the resource/span scope boundary and parses awkwardly in TraceQL.
|
||||
5. **Span names** use `<subsystem>[.<component>]` (dotted, per §2.3.1). Only
|
||||
attribute _keys_ follow rules 1–4.
|
||||
|
||||
Standard OpenTelemetry semantic-convention keys keep their canonical dotted
|
||||
form (e.g. `service.*` resource attributes, `http.*` span attributes); the
|
||||
"no dotted form" rule applies to xrpl-custom keys only.
|
||||
|
||||
The same rules are recorded in `CONTRIBUTING.md` (the permanent home, since
|
||||
`OpenTelemetryPlan/` is removed once the rollout completes). The attribute
|
||||
examples in §2.4 below follow these rules.
|
||||
|
||||
---
|
||||
|
||||
## 2.4 Attribute Schema
|
||||
|
||||
> **TxQ** = Transaction Queue | **UNL** = Unique Node List | **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
### 2.4.1 Resource Attributes (Set Once at Startup)
|
||||
|
||||
Resource attributes identify the process and are set once at startup. They use
|
||||
the standard OpenTelemetry semantic conventions plus custom dotted `xrpl.*`
|
||||
keys (the dotted form is reserved for resource scope per §2.3.3).
|
||||
|
||||
| Key | Type / value | Description |
|
||||
| --------------------- | ------------------------------------------------------- | ------------------------------ |
|
||||
| `service.name` | `"xrpld"` | Standard `SERVICE_NAME` |
|
||||
| `service.version` | `BuildInfo::getVersionString()` | Standard `SERVICE_VERSION` |
|
||||
| `service.instance.id` | node public key (base58) | Standard `SERVICE_INSTANCE_ID` |
|
||||
| `xrpl.network.id` | network id (e.g. 0 for mainnet) | Network identifier |
|
||||
| `xrpl.network.type` | `"mainnet"` \| `"testnet"` \| `"devnet"` \| `"unknown"` | Network kind |
|
||||
| `xrpl.node.type` | `"validator"` \| `"stock"` \| `"reporting"` | Node role |
|
||||
| `xrpl.node.cluster` | cluster name | Cluster name, if clustered |
|
||||
|
||||
### 2.4.2 Span Attributes by Category
|
||||
|
||||
> Span attribute keys use the underscore form from §2.3.3 (shared/qualified
|
||||
> keys are `<domain>_<field>`; per-span unique keys are bare). The dotted form
|
||||
> is reserved for the resource attributes in §2.4.1 above. This catalog lists
|
||||
> the planned attribute set by category; the exact emitted key for each
|
||||
> implemented span is defined by the `*SpanNames.h` constants, which are the
|
||||
> single source of truth where the two differ.
|
||||
|
||||
#### Transaction Attributes
|
||||
|
||||
| Key | Type | Description |
|
||||
| -------------- | ------ | ------------------------------------- |
|
||||
| `tx_hash` | string | Transaction hash (hex) |
|
||||
| `tx_type` | string | `"Payment"`, `"OfferCreate"`, etc. |
|
||||
| `tx_account` | string | Source account (redacted in prod) |
|
||||
| `tx_sequence` | int64 | Account sequence number |
|
||||
| `tx_fee` | int64 | Fee in drops |
|
||||
| `tx_result` | string | `"tesSUCCESS"`, `"tecPATH_DRY"`, etc. |
|
||||
| `ledger_index` | int64 | Ledger containing transaction |
|
||||
|
||||
#### Consensus Attributes
|
||||
|
||||
| Key | Type | Description |
|
||||
| -------------------- | ------- | ----------------------------------- |
|
||||
| `consensus_round` | int64 | Round number |
|
||||
| `consensus_phase` | string | `"open"`, `"establish"`, `"accept"` |
|
||||
| `consensus_mode` | string | `"proposing"`, `"observing"`, etc. |
|
||||
| `proposers` | int64 | Number of proposers |
|
||||
| `prev_ledger_prefix` | string | Previous ledger hash prefix |
|
||||
| `ledger_seq` | int64 | Ledger sequence |
|
||||
| `tx_count` | int64 | Transactions in consensus set |
|
||||
| `round_time_ms` | float64 | Round duration |
|
||||
|
||||
Establish-phase gap fill and cross-node correlation attributes (Phase 4a):
|
||||
|
||||
| Key | Type | Description |
|
||||
| --------------------- | ------ | --------------------------------------------------------- |
|
||||
| `consensus_round_id` | int64 | Consensus round number |
|
||||
| `consensus_ledger_id` | string | `previousLedger.id()` — shared across nodes |
|
||||
| `trace_strategy` | string | `"deterministic"` or `"attribute"` |
|
||||
| `converge_percent` | int64 | Convergence % (0-100+) |
|
||||
| `establish_count` | int64 | Number of establish iterations |
|
||||
| `disputes_count` | int64 | Active disputed transactions |
|
||||
| `agree_count` | int64 | Peers that agree (haveConsensus) |
|
||||
| `disagree_count` | int64 | Peers that disagree |
|
||||
| `threshold_percent` | int64 | Close-time consensus threshold (`avCT_CONSENSUS_PCT`=75%) |
|
||||
| `consensus_result` | string | `"yes"`, `"no"`, `"moved_on"`, `"expired"` |
|
||||
| `mode_old` | string | Previous consensus mode |
|
||||
| `mode_new` | string | New consensus mode |
|
||||
|
||||
#### RPC Attributes
|
||||
|
||||
| Key | Type | Description |
|
||||
| ---------- | ------ | ----------------------------------------------------- |
|
||||
| `command` | string | Command name (per-span unique on `rpc.command`) |
|
||||
| `version` | int64 | API version |
|
||||
| `rpc_role` | string | `"admin"` or `"user"` (qualified — `role` is generic) |
|
||||
| `params` | string | Sanitized parameters (optional) |
|
||||
|
||||
#### Peer & Message Attributes
|
||||
|
||||
| Key | Type | Description |
|
||||
| -------------------- | ------- | -------------------------- |
|
||||
| `peer_id` | string | Peer public key (base58) |
|
||||
| `peer_address` | string | IP:port |
|
||||
| `peer_latency_ms` | float64 | Measured latency |
|
||||
| `peer_cluster` | string | Cluster name if clustered |
|
||||
| `message_type` | string | Protocol message type name |
|
||||
| `message_size_bytes` | int64 | Message size |
|
||||
| `message_compressed` | bool | Whether compressed |
|
||||
|
||||
#### Ledger & Job Attributes
|
||||
|
||||
| Key | Type | Description |
|
||||
| ----------------- | ------- | --------------------- |
|
||||
| `ledger_hash` | string | Ledger hash |
|
||||
| `ledger_index` | int64 | Ledger sequence/index |
|
||||
| `close_time` | int64 | Close time (epoch) |
|
||||
| `ledger_tx_count` | int64 | Transaction count |
|
||||
| `job_type` | string | Job type name |
|
||||
| `job_queue_ms` | float64 | Time spent in queue |
|
||||
| `job_worker` | int64 | Worker thread ID |
|
||||
|
||||
#### PathFinding Attributes
|
||||
|
||||
| Key | Type | Description |
|
||||
| -------------------------- | ------ | ------------------------- |
|
||||
| `pathfind_source_currency` | string | Source currency code |
|
||||
| `pathfind_dest_currency` | string | Destination currency code |
|
||||
| `pathfind_path_count` | int64 | Number of paths found |
|
||||
| `pathfind_cache_hit` | bool | RippleLineCache hit |
|
||||
|
||||
#### TxQ Attributes
|
||||
|
||||
| Key | Type | Description |
|
||||
| --------------------- | ------ | --------------------------- |
|
||||
| `txq_queue_depth` | int64 | Current queue depth |
|
||||
| `txq_fee_level` | int64 | Fee level of transaction |
|
||||
| `txq_eviction_reason` | string | Why transaction was evicted |
|
||||
|
||||
#### Fee Attributes
|
||||
|
||||
| Key | Type | Description |
|
||||
| ---------------------- | ----- | ------------------------- |
|
||||
| `fee_load_factor` | int64 | Current load factor |
|
||||
| `fee_escalation_level` | int64 | Fee escalation multiplier |
|
||||
|
||||
#### Validator Attributes
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------------------------ | ----- | ------------------------- |
|
||||
| `validator_list_size` | int64 | UNL size |
|
||||
| `validator_list_age_sec` | int64 | Seconds since last update |
|
||||
|
||||
#### Amendment Attributes
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------------------ | ------ | -------------------------------------- |
|
||||
| `amendment_name` | string | Amendment name |
|
||||
| `amendment_status` | string | `"enabled"`, `"vetoed"`, `"supported"` |
|
||||
|
||||
#### SHAMap Attributes
|
||||
|
||||
| Key | Type | Description |
|
||||
| ---------------------- | ------- | --------------------------------------------- |
|
||||
| `shamap_type` | string | `"transaction"`, `"state"`, `"account_state"` |
|
||||
| `shamap_missing_nodes` | int64 | Number of missing nodes during sync |
|
||||
| `shamap_duration_ms` | float64 | Sync duration |
|
||||
|
||||
### 2.4.3 Data Collection Summary
|
||||
|
||||
The following table summarizes what data is collected by category:
|
||||
|
||||
| Category | Attributes Collected | Purpose |
|
||||
| --------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------------- |
|
||||
| **Transaction** | `tx_hash`, `tx_type`, `tx_result`, `tx_fee`, `ledger_index` | Trace transaction lifecycle |
|
||||
| **Consensus** | `consensus_round`, `consensus_phase`, `consensus_mode`, `proposers`, `round_time_ms` | Analyze consensus timing |
|
||||
| **RPC** | `command`, `version`, `rpc_status`, `duration_ms` | Monitor RPC performance |
|
||||
| **Peer** | `peer_id` (public key), `peer_latency_ms`, `message_type`, `message_size_bytes` | Network topology analysis |
|
||||
| **Ledger** | `ledger_hash`, `ledger_index`, `close_time`, `ledger_tx_count` | Ledger progression tracking |
|
||||
| **Job** | `job_type`, `job_queue_ms`, `job_worker` | JobQueue performance |
|
||||
| **PathFinding** | `pathfind_fast`, `pathfind_search_level`, `pathfind_num_paths`, `pathfind_ledger_index`, `pathfind_num_requests` | Payment path analysis |
|
||||
| **TxQ** | `txq_queue_depth`, `txq_fee_level`, `txq_eviction_reason` | Queue depth and fee tracking |
|
||||
| **Fee** | `fee_load_factor`, `fee_escalation_level` | Fee escalation monitoring |
|
||||
| **Validator** | `validator_list_size`, `validator_list_age_sec` | UNL health monitoring |
|
||||
| **Amendment** | `amendment_name`, `amendment_status` | Protocol upgrade tracking |
|
||||
| **SHAMap** | `shamap_type`, `shamap_missing_nodes`, `shamap_duration_ms` | State tree sync performance |
|
||||
|
||||
### 2.4.4 Privacy & Sensitive Data Policy
|
||||
|
||||
> **PII** = Personally Identifiable Information
|
||||
|
||||
OpenTelemetry instrumentation is designed to collect **operational metadata only**, never sensitive content.
|
||||
|
||||
#### Data NOT Collected
|
||||
|
||||
The following data is explicitly **excluded** from telemetry collection:
|
||||
|
||||
| Excluded Data | Reason |
|
||||
| ----------------------- | ----------------------------------------- |
|
||||
| **Private Keys** | Never exposed; not relevant to tracing |
|
||||
| **Account Balances** | Financial data; privacy sensitive |
|
||||
| **Transaction Amounts** | Financial data; privacy sensitive |
|
||||
| **Raw TX Payloads** | May contain sensitive memo/data fields |
|
||||
| **Personal Data** | No PII collected |
|
||||
| **IP Addresses** | Configurable; excluded by default in prod |
|
||||
|
||||
#### Privacy Protection Mechanisms
|
||||
|
||||
| Mechanism | Description |
|
||||
| ----------------------------- | ------------------------------------------------------------------------- |
|
||||
| **Account Hashing** | `tx_account` is hashed at collector level before storage |
|
||||
| **Configurable Redaction** | Sensitive fields can be excluded via `[telemetry]` config section |
|
||||
| **Sampling** | Only 10% of traces recorded by default, reducing data exposure |
|
||||
| **Local Control** | Node operators have full control over what gets exported |
|
||||
| **No Raw Payloads** | Transaction content is never recorded, only metadata (hash, type, result) |
|
||||
| **Collector-Level Filtering** | Additional redaction/hashing can be configured at OTel Collector |
|
||||
|
||||
#### Collector-Level Data Protection
|
||||
|
||||
The OpenTelemetry Collector can be configured (via an `attributes` processor)
|
||||
to hash or redact sensitive attributes before export — for example, hashing
|
||||
`tx_account`, deleting `peer_address` to drop IP addresses, and deleting
|
||||
`params` to redact request parameters.
|
||||
|
||||
#### Configuration Options for Privacy
|
||||
|
||||
In `xrpld.cfg`, operators control data collection granularity through the
|
||||
`[telemetry]` section. Besides `enabled`, per-component toggles
|
||||
(`trace_transactions`, `trace_consensus`, `trace_rpc`, `trace_peer` — the last
|
||||
often disabled due to high volume) select which spans are emitted, and
|
||||
redaction flags (`redact_account` to hash account addresses, `redact_peer_address`
|
||||
to remove peer IP addresses) control SDK-level redaction before export.
|
||||
|
||||
> **Note**: The `redact_account` configuration in `xrpld.cfg` controls SDK-level redaction before export, while collector-level filtering (see [Collector-Level Data Protection](#collector-level-data-protection) above) provides an additional defense-in-depth layer. Both can operate independently.
|
||||
|
||||
> **Key Principle**: Telemetry collects **operational metadata** (timing, counts, hashes) — never **sensitive content** (keys, balances, amounts, raw payloads).
|
||||
|
||||
> **See also**: [Securing the OTel Pipeline](./secure-OTel.md) covers transport-level protection for telemetry leaving the node — mTLS to the collector and validation of incoming peer trace context. Privacy controls in this section keep sensitive data out of spans; the security doc keeps the spans themselves out of untrusted hands.
|
||||
|
||||
---
|
||||
|
||||
## 2.5 Context Propagation Design
|
||||
|
||||
> **WS** = WebSocket
|
||||
|
||||
### 2.5.0 Deterministic Trace ID Strategy
|
||||
|
||||
Both transaction and consensus tracing use **deterministic trace IDs** derived from
|
||||
a globally known hash, so all nodes handling the same workflow independently produce
|
||||
spans under the same `trace_id`. This is combined with protobuf `span_id` propagation
|
||||
for parent-child relay ordering when available.
|
||||
|
||||
#### Transactions — `trace_id = txHash[0:16]`
|
||||
|
||||
Every node that handles a transaction knows its `txID` (the `uint256` transaction
|
||||
hash). The first 16 bytes of this hash are used as the OTel `trace_id`:
|
||||
|
||||
```
|
||||
uint256 txHash: A1B2C3D4 E5F6A7B8 C9D0E1F2 A3B4C5D6 E7F8A9B0 C1D2E3F4 A5B6C7D8 E9F0A1B2
|
||||
|---------- trace_id (16 bytes) ---------| (remaining 16 bytes unused)
|
||||
```
|
||||
|
||||
Each node generates a **random 8-byte `span_id`** so its span is unique within the
|
||||
shared trace. When protobuf `TraceContext` is present in the incoming `TMTransaction`,
|
||||
the sender's `span_id` is extracted and used as the parent — preserving the relay
|
||||
chain as a parent-child tree. When absent (older peers, first hop from client), the
|
||||
span appears as a root in the same trace — correlation is preserved, only the tree
|
||||
structure degrades.
|
||||
|
||||
```
|
||||
Node A (submitter) Node B (relay) Node C (relay)
|
||||
trace_id: A1B2... trace_id: A1B2... trace_id: A1B2...
|
||||
span_id: 1234 (random) span_id: 5678 (random) span_id: 9ABC (random)
|
||||
parent: (none) parent: 1234 (proto) parent: 5678 (proto)
|
||||
↑ ↑
|
||||
protobuf propagation protobuf propagation
|
||||
```
|
||||
|
||||
If protobuf propagation fails at Node B (old peer):
|
||||
|
||||
```
|
||||
Node A Node B (old peer) Node C
|
||||
trace_id: A1B2... trace_id: A1B2... trace_id: A1B2...
|
||||
span_id: 1234 span_id: 5678 span_id: 9ABC
|
||||
parent: (none) parent: (none) parent: 5678 (proto)
|
||||
↑ no parent, but same trace_id — still grouped
|
||||
```
|
||||
|
||||
#### Consensus — `trace_id = prevLedgerHash[0:16]`
|
||||
|
||||
All validators in the same consensus round share the same `previousLedger.id()`.
|
||||
The first 16 bytes are used as trace_id. See [Phase 4a implementation status](./06-implementation-phases.md)
|
||||
and `createDeterministicContext()` in `RCLConsensus.cpp` for the implementation.
|
||||
|
||||
Switchable via `consensus_trace_strategy` config:
|
||||
`"deterministic"` (default) or `"attribute"` (random trace_id, correlation via attribute queries).
|
||||
|
||||
#### Why Not Random IDs with Propagation Only?
|
||||
|
||||
Random trace IDs require **unbroken context propagation** across every hop. In a
|
||||
mixed-version network (common during upgrades), older peers silently drop the
|
||||
`trace_context` protobuf field. The trace splits and downstream spans become
|
||||
impossible to find. Deterministic IDs make correlation **propagation-resilient** — the trace
|
||||
backend groups all spans for the same transaction/round regardless of whether
|
||||
propagation succeeded.
|
||||
|
||||
#### Why Keep Protobuf Propagation?
|
||||
|
||||
Deterministic trace IDs alone provide correlation (all spans grouped) but not
|
||||
**causality** (which node relayed to which). Protobuf `span_id` propagation adds
|
||||
parent-child ordering that shows the exact relay path. The two mechanisms complement
|
||||
each other:
|
||||
|
||||
| Mechanism | Provides | Fails when |
|
||||
| ---------------------------- | --------------------------- | -------------------------------------- |
|
||||
| Deterministic trace_id | Cross-node correlation | Never (hash is always known) |
|
||||
| Protobuf span_id propagation | Parent-child relay ordering | Older peer drops `trace_context` field |
|
||||
|
||||
#### Implementation Reference
|
||||
|
||||
The utility function `createDeterministicTxContext(uint256 const& txHash)` follows
|
||||
the same pattern as `createDeterministicContext(uint256 const& ledgerId)` in
|
||||
`RCLConsensus.cpp`. See [Phase 3 Task 3.9](./Phase3_taskList.md) for the full spec.
|
||||
|
||||
### 2.5.1 Propagation Boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph http["HTTP/WebSocket (RPC)"]
|
||||
w3c["W3C Trace Context Headers:<br/>traceparent:<br/>00-trace_id-span_id-flags<br/>tracestate: xrpld=..."]
|
||||
end
|
||||
|
||||
subgraph protobuf["Protocol Buffers (P2P)"]
|
||||
proto["message TraceContext {<br/> bytes trace_id = 1; // 16 bytes<br/> bytes span_id = 2; // 8 bytes<br/> uint32 trace_flags = 3;<br/> string trace_state = 4;<br/>}"]
|
||||
end
|
||||
|
||||
subgraph jobqueue["JobQueue (Internal Async)"]
|
||||
job["Context captured at job creation,<br/>restored at execution<br/><br/>class Job {<br/> otel::context::Context<br/> traceContext_;<br/>};"]
|
||||
end
|
||||
|
||||
style http fill:#0d47a1,stroke:#082f6a,color:#ffffff
|
||||
style protobuf fill:#1b5e20,stroke:#0d3d14,color:#ffffff
|
||||
style jobqueue fill:#bf360c,stroke:#8c2809,color:#ffffff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **HTTP/WebSocket - RPC (blue)**: For client-facing RPC requests, trace context is propagated using the W3C `traceparent` header. This is the standard approach and works with any OTel-compatible client.
|
||||
- **Protocol Buffers - P2P (green)**: For peer-to-peer messages between xrpld nodes, trace context is embedded as a protobuf `TraceContext` message carrying trace_id, span_id, flags, and optional trace_state.
|
||||
- **JobQueue - Internal Async (red)**: For asynchronous work within a single node, the OTel context is captured when a job is created and restored when the job executes on a worker thread. This bridges the async gap so spans remain linked.
|
||||
|
||||
---
|
||||
|
||||
## 2.6 Integration with Existing Observability
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol | **WS** = WebSocket
|
||||
|
||||
### 2.6.1 Existing Frameworks Comparison
|
||||
|
||||
xrpld already has two observability mechanisms. OpenTelemetry complements (not replaces) them:
|
||||
|
||||
| Aspect | PerfLog | Beast Insight (StatsD) | OpenTelemetry |
|
||||
| --------------------- | ----------------------------- | ---------------------------- | ------------------------- |
|
||||
| **Type** | Logging | Metrics | Distributed Tracing |
|
||||
| **Data** | JSON log entries | Counters, gauges, histograms | Spans with context |
|
||||
| **Scope** | Single node | Single node | **Cross-node** |
|
||||
| **Output** | `perf.log` file | StatsD server | OTLP Collector |
|
||||
| **Question answered** | "What happened on this node?" | "How many? How fast?" | "What was the journey?" |
|
||||
| **Correlation** | By timestamp | By metric name | By `trace_id` |
|
||||
| **Overhead** | Low (file I/O) | Low (UDP packets) | Low-Medium (configurable) |
|
||||
|
||||
### 2.6.2 What Each Framework Does Best
|
||||
|
||||
#### PerfLog
|
||||
|
||||
- **Purpose**: Detailed local event logging for RPC and job execution
|
||||
- **Strengths**:
|
||||
- Rich JSON output with timing data
|
||||
- Already integrated in RPC handlers
|
||||
- File-based, no external dependencies
|
||||
- **Limitations**:
|
||||
- Single-node only (no cross-node correlation)
|
||||
- No parent-child relationships between events
|
||||
- Manual log parsing required
|
||||
|
||||
A PerfLog entry is a JSON object with fields such as `time`, `method`,
|
||||
`duration_us`, and `result`.
|
||||
|
||||
#### Beast Insight (StatsD)
|
||||
|
||||
- **Purpose**: Real-time metrics for monitoring dashboards
|
||||
- **Strengths**:
|
||||
- Aggregated metrics (counters, gauges, histograms)
|
||||
- Low overhead (UDP, fire-and-forget)
|
||||
- Good for alerting thresholds
|
||||
- **Limitations**:
|
||||
- No request-level detail
|
||||
- No causal relationships
|
||||
- Single-node perspective
|
||||
|
||||
In xrpld, Beast Insight is used through `increment` (counters), `gauge`
|
||||
(point-in-time values), and `timing` (durations) calls.
|
||||
|
||||
#### OpenTelemetry (NEW)
|
||||
|
||||
- **Purpose**: Distributed request tracing across nodes
|
||||
- **Strengths**:
|
||||
- **Cross-node correlation** via `trace_id`
|
||||
- Parent-child span relationships
|
||||
- Rich attributes per span
|
||||
- Industry standard (CNCF)
|
||||
- **Limitations**:
|
||||
- Requires collector infrastructure
|
||||
- Higher complexity than logging
|
||||
|
||||
A span is created via `startSpan` (e.g. `"tx.relay"`), annotated with
|
||||
attributes such as `tx_hash` and `peer_id`, and is automatically linked to its
|
||||
parent through the active context.
|
||||
|
||||
### 2.6.3 When to Use Each
|
||||
|
||||
| Scenario | PerfLog | StatsD | OpenTelemetry |
|
||||
| --------------------------------------- | ---------- | ------ | ------------- |
|
||||
| "How many TXs per second?" | ❌ | ✅ | ✅ |
|
||||
| "What's the p99 RPC latency?" | ❌ | ✅ | ✅ |
|
||||
| "Why was this specific TX slow?" | ⚠️ partial | ❌ | ✅ |
|
||||
| "Which node delayed consensus?" | ❌ | ❌ | ✅ |
|
||||
| "What happened on node X at time T?" | ✅ | ❌ | ✅ |
|
||||
| "Show me the TX journey across 5 nodes" | ❌ | ❌ | ✅ |
|
||||
|
||||
### 2.6.4 Coexistence Strategy
|
||||
|
||||
> **Note**: Phase 7 replaces the StatsD bridge with native OTel Metrics SDK export. The diagram below shows the Phase 6 intermediate state. See [Phase7_taskList.md](./Phase7_taskList.md) for the migration design where Beast Insight emits via OTLP instead of StatsD.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph xrpld["xrpld Process"]
|
||||
perflog["PerfLog<br/>(JSON to file)"]
|
||||
insight["Beast Insight<br/>(StatsD)"]
|
||||
otel["OpenTelemetry<br/>(Tracing)"]
|
||||
end
|
||||
|
||||
perflog --> perffile["perf.log"]
|
||||
insight --> statsd["StatsD Server"]
|
||||
otel --> collector["OTLP Collector"]
|
||||
|
||||
perffile --> grafana["Grafana<br/>(Unified UI)"]
|
||||
statsd --> grafana
|
||||
collector --> grafana
|
||||
|
||||
style xrpld fill:#212121,stroke:#0a0a0a,color:#ffffff
|
||||
style grafana fill:#bf360c,stroke:#8c2809,color:#ffffff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **xrpld Process (dark gray)**: The single xrpld node running all three observability frameworks side by side. Each framework operates independently with no interference.
|
||||
- **PerfLog to perf.log**: PerfLog writes JSON-formatted event logs to a local file. Grafana can ingest these via Loki or a file-based datasource.
|
||||
- **Beast Insight to StatsD Server**: Insight sends aggregated metrics (counters, gauges) over UDP to a StatsD server. Grafana reads from StatsD-compatible backends like Graphite or Prometheus (via StatsD exporter).
|
||||
- **OpenTelemetry to OTLP Collector**: OTel exports spans over OTLP/gRPC to a Collector, which then forwards to a trace backend (Tempo).
|
||||
- **Grafana (red, unified UI)**: All three data streams converge in Grafana, enabling operators to correlate logs, metrics, and traces in a single dashboard.
|
||||
|
||||
**Phase 7 target state**: Beast Insight routes to `OTelCollector` (new `Collector` implementation) which exports via OTLP/HTTP to the same collector endpoint as traces. StatsD UDP path becomes a deprecated fallback (`[insight] server=statsd`). See [06-implementation-phases.md §6.8](./06-implementation-phases.md) and [Phase7_taskList.md](./Phase7_taskList.md) for details.
|
||||
|
||||
### 2.6.5 Correlation with PerfLog
|
||||
|
||||
Trace IDs can be correlated with existing PerfLog entries for comprehensive
|
||||
debugging. The design is for `RPCHandler.cpp` to start an `rpc.command.<method>`
|
||||
span alongside the existing PerfLog `rpcStart`/`rpcFinish`/`rpcError` calls,
|
||||
extract the span's `trace_id` (when valid), and eventually stamp it onto the
|
||||
PerfLog entry (a planned `setTraceId` hook) so logs and traces share a key. The
|
||||
span status is set to OK on success or to error (recording the exception) on
|
||||
failure.
|
||||
|
||||
---
|
||||
|
||||
_Previous: [Architecture Analysis](./01-architecture-analysis.md)_ | _Next: [Implementation Strategy](./03-implementation-strategy.md)_ | _Back to: [Overview](./OpenTelemetryPlan.md)_
|
||||
483
OpenTelemetryPlan/03-implementation-strategy.md
Normal file
483
OpenTelemetryPlan/03-implementation-strategy.md
Normal file
@@ -0,0 +1,483 @@
|
||||
# Implementation Strategy
|
||||
|
||||
> **Parent Document**: [OpenTelemetryPlan.md](./OpenTelemetryPlan.md)
|
||||
> **Related**: [Configuration Reference](./05-configuration-reference.md)
|
||||
|
||||
---
|
||||
|
||||
## 3.1 Directory Structure
|
||||
|
||||
The telemetry implementation follows xrpld's existing code organization pattern:
|
||||
|
||||
```
|
||||
include/xrpl/
|
||||
├── telemetry/
|
||||
│ ├── Telemetry.h # Main telemetry interface (global singleton)
|
||||
│ ├── TelemetryConfig.h # Configuration structures
|
||||
│ ├── TraceContext.h # Context propagation utilities
|
||||
│ ├── SpanGuard.h # RAII span management with factory methods + discard()
|
||||
│ ├── DiscardFlag.h # Thread-local discard flag
|
||||
│ └── SpanAttributes.h # Attribute helper functions
|
||||
|
||||
src/libxrpl/
|
||||
├── telemetry/
|
||||
│ ├── Telemetry.cpp # Implementation + FilteringSpanProcessor
|
||||
│ ├── TelemetryConfig.cpp # Config parsing
|
||||
│ ├── TraceContext.cpp # Context serialization
|
||||
│ └── NullTelemetry.cpp # No-op implementation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3.2 Implementation Approach
|
||||
|
||||
<div align="center">
|
||||
|
||||
```mermaid
|
||||
%%{init: {'flowchart': {'nodeSpacing': 20, 'rankSpacing': 30}}}%%
|
||||
flowchart TB
|
||||
subgraph phase1["Phase 1: Core"]
|
||||
direction LR
|
||||
sdk["SDK Integration"] ~~~ interface["Telemetry Interface"] ~~~ config["Configuration"]
|
||||
end
|
||||
|
||||
subgraph phase2["Phase 2: RPC"]
|
||||
direction LR
|
||||
http["HTTP Context"] ~~~ rpc["RPC Handlers"]
|
||||
end
|
||||
|
||||
subgraph phase3["Phase 3: P2P"]
|
||||
direction LR
|
||||
proto["Protobuf Context"] ~~~ tx["Transaction Relay"]
|
||||
end
|
||||
|
||||
subgraph phase4["Phase 4: Consensus"]
|
||||
direction LR
|
||||
consensus["Consensus Rounds"] ~~~ proposals["Proposals"]
|
||||
end
|
||||
|
||||
phase1 --> phase2 --> phase3 --> phase4
|
||||
|
||||
style phase1 fill:#1565c0,stroke:#0d47a1,color:#ffffff
|
||||
style phase2 fill:#2e7d32,stroke:#1b5e20,color:#ffffff
|
||||
style phase3 fill:#e65100,stroke:#bf360c,color:#ffffff
|
||||
style phase4 fill:#c2185b,stroke:#880e4f,color:#ffffff
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
### Key Principles
|
||||
|
||||
1. **Minimal Intrusion**: Instrumentation should not alter existing control flow
|
||||
2. **Zero-Cost When Disabled**: Use compile-time flags and no-op implementations
|
||||
3. **Backward Compatibility**: Protocol Buffer extensions use high field numbers
|
||||
4. **Graceful Degradation**: Tracing failures must not affect node operation
|
||||
|
||||
---
|
||||
|
||||
## 3.3 Performance Overhead Summary
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
| Metric | Overhead | Notes |
|
||||
| ------------- | ---------- | ------------------------------------------------ |
|
||||
| CPU | 1-3% | Of per-transaction CPU cost (~200μs baseline) |
|
||||
| Memory | ~10 MB | SDK statics + batch buffer + worker thread stack |
|
||||
| Network | 10-50 KB/s | Compressed OTLP export to collector |
|
||||
| Latency (p99) | <2% | With proper sampling configuration |
|
||||
|
||||
---
|
||||
|
||||
## 3.4 Detailed CPU Overhead Analysis
|
||||
|
||||
### 3.4.1 Per-Operation Costs
|
||||
|
||||
> **Note on hardware assumptions**: The costs below are based on the official OTel C++ SDK CI benchmarks
|
||||
> (969 runs on GitHub Actions 2-core shared runners). On production server hardware (3+ GHz Xeon),
|
||||
> expect costs at the **lower end** of each range (~30-50% improvement over CI hardware).
|
||||
|
||||
| Operation | Time (ns) | Frequency | Impact |
|
||||
| --------------------- | --------- | ---------------------- | ---------- |
|
||||
| Span creation | 500-1000 | Every traced operation | Low |
|
||||
| Span end | 100-200 | Every traced operation | Low |
|
||||
| SetAttribute (string) | 80-120 | 3-5 per span | Low |
|
||||
| SetAttribute (int) | 40-60 | 2-3 per span | Negligible |
|
||||
| AddEvent | 100-200 | 0-2 per span | Low |
|
||||
| Context injection | 150-250 | Per outgoing message | Low |
|
||||
| Context extraction | 100-180 | Per incoming message | Low |
|
||||
| GetCurrent context | 10-20 | Thread-local access | Negligible |
|
||||
|
||||
**Source**: Span creation based on OTel C++ SDK `BM_SpanCreation` benchmark (AlwaysOnSampler +
|
||||
SimpleSpanProcessor + InMemoryExporter), median ~1,000 ns on CI hardware. AddEvent includes
|
||||
timestamp read + string copy + vector push + mutex acquisition. Context injection/extraction
|
||||
confirmed by `BM_SpanCreationWithScope` benchmark delta (~160 ns).
|
||||
|
||||
### 3.4.2 Transaction Processing Overhead
|
||||
|
||||
<div align="center">
|
||||
|
||||
```mermaid
|
||||
%%{init: {'pie': {'textPosition': 0.75}}}%%
|
||||
pie showData
|
||||
"tx.receive (1400ns)" : 1400
|
||||
"tx.validate (1200ns)" : 1200
|
||||
"tx.relay (1200ns)" : 1200
|
||||
"Context inject (200ns)" : 200
|
||||
```
|
||||
|
||||
**Transaction Tracing Overhead (~4.0μs total)**
|
||||
|
||||
</div>
|
||||
|
||||
**Overhead percentage**: 4.0 μs / 200 μs (avg tx processing) = **~2.0%**
|
||||
|
||||
> **Breakdown**: Each span (tx.receive, tx.validate, tx.relay) costs ~1,000 ns for creation plus
|
||||
> ~200-400 ns for 3-5 attribute sets. Context injection is ~200 ns (confirmed by benchmarks).
|
||||
> On production hardware, expect ~2.6 μs total (~1.3% overhead) due to faster span creation (~500-600 ns).
|
||||
|
||||
### 3.4.3 Consensus Round Overhead
|
||||
|
||||
| Operation | Count | Cost (ns) | Total |
|
||||
| ---------------------- | ----- | --------- | ---------- |
|
||||
| consensus.round span | 1 | ~1200 | ~1.2 μs |
|
||||
| consensus.phase spans | 3 | ~1100 | ~3.3 μs |
|
||||
| proposal.receive spans | ~20 | ~1100 | ~22 μs |
|
||||
| proposal.send spans | ~3 | ~1100 | ~3.3 μs |
|
||||
| Context operations | ~30 | ~200 | ~6 μs |
|
||||
| **TOTAL** | | | **~36 μs** |
|
||||
|
||||
> **Why higher**: Each span costs ~1,000 ns creation + ~100-200 ns for 1-2 attributes, totaling ~1,100-1,200 ns.
|
||||
> Context operations remain ~200 ns (confirmed by benchmarks). On production hardware, expect ~24 μs total.
|
||||
|
||||
**Overhead percentage**: 36 μs / 3s (typical round) = **~0.001%** (negligible)
|
||||
|
||||
### 3.4.4 RPC Request Overhead
|
||||
|
||||
| Operation | Cost (ns) |
|
||||
| ---------------- | ------------ |
|
||||
| rpc.request span | ~1200 |
|
||||
| rpc.command span | ~1100 |
|
||||
| Context extract | ~250 |
|
||||
| Context inject | ~200 |
|
||||
| **TOTAL** | **~2.75 μs** |
|
||||
|
||||
> **Why higher**: Each span costs ~1,000 ns creation + ~100-200 ns for attributes (command name,
|
||||
> version, role). Context extract/inject costs are confirmed by OTel C++ benchmarks.
|
||||
|
||||
- Fast RPC (1ms): 2.75 μs / 1ms = **~0.275%**
|
||||
- Slow RPC (100ms): 2.75 μs / 100ms = **~0.003%**
|
||||
|
||||
---
|
||||
|
||||
## 3.5 Memory Overhead Analysis
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
### 3.5.1 Static Memory
|
||||
|
||||
| Component | Size | Allocated |
|
||||
| ------------------------------------ | ----------- | ---------- |
|
||||
| TracerProvider singleton | ~64 KB | At startup |
|
||||
| BatchSpanProcessor (circular buffer) | ~16 KB | At startup |
|
||||
| BatchSpanProcessor (worker thread) | ~8 MB | At startup |
|
||||
| OTLP exporter (gRPC channel init) | ~256 KB | At startup |
|
||||
| Propagator registry | ~8 KB | At startup |
|
||||
| **Total static** | **~8.3 MB** | |
|
||||
|
||||
> **Why higher than earlier estimate**: The BatchSpanProcessor's circular buffer itself is only ~16 KB
|
||||
> (2049 x 8-byte `AtomicUniquePtr` entries), but it spawns a dedicated worker thread whose default
|
||||
> stack size on Linux is ~8 MB. The OTLP gRPC exporter allocates memory for channel stubs and TLS
|
||||
> initialization. The worker thread stack dominates the static footprint.
|
||||
|
||||
### 3.5.2 Dynamic Memory
|
||||
|
||||
| Component | Size per unit | Max units | Peak |
|
||||
| -------------------- | -------------- | ---------- | --------------- |
|
||||
| Active span | ~500-800 bytes | 1000 | ~500-800 KB |
|
||||
| Queued span (export) | ~500 bytes | 2048 | ~1 MB |
|
||||
| Attribute storage | ~80 bytes | 5 per span | Included |
|
||||
| Context storage | ~64 bytes | Per thread | ~6.4 KB |
|
||||
| **Total dynamic** | | | **~1.5-1.8 MB** |
|
||||
|
||||
> **Why active spans are larger**: An active `Span` object includes the wrapper (~88 bytes: shared_ptr,
|
||||
> mutex, unique_ptr to Recordable) plus `SpanData` (~250 bytes: SpanContext, timestamps, name, status,
|
||||
> empty containers) plus attribute storage (~200-500 bytes for 3-5 string attributes in a `std::map`).
|
||||
> Source: `sdk/src/trace/span.h` and `sdk/include/opentelemetry/sdk/trace/span_data.h`.
|
||||
> Queued spans release the wrapper, keeping only `SpanData` + attributes (~500 bytes).
|
||||
|
||||
### 3.5.3 Memory Growth Characteristics
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
xyChart:
|
||||
width: 700
|
||||
height: 400
|
||||
---
|
||||
xychart-beta
|
||||
title "Memory Usage vs Span Rate (bounded by queue limit)"
|
||||
x-axis "Spans/second" [0, 200, 400, 600, 800, 1000]
|
||||
y-axis "Memory (MB)" 0 --> 12
|
||||
line [8.5, 9.2, 9.6, 9.9, 10.0, 10.0]
|
||||
```
|
||||
|
||||
**Notes**:
|
||||
|
||||
- Memory increases with span rate but **plateaus at queue capacity** (default 2048 spans)
|
||||
- Batch export prevents unbounded growth
|
||||
- At queue limit, oldest spans are dropped (not blocked)
|
||||
- Maximum memory is bounded: ~8.3 MB static (dominated by worker thread stack) + 2048 queued spans x ~500 bytes (~1 MB) + active spans (~0.8 MB) ≈ **~10 MB ceiling**
|
||||
- The worker thread stack (~8 MB) is virtual memory; actual RSS depends on stack usage (typically much less)
|
||||
|
||||
> **Measured outcome**: A perf-iac comparison (telemetry compiled-in + enabled vs compiled-out,
|
||||
> 9 nodes — validators and client-handlers — under sustained payment load) recorded **no measurable
|
||||
> RSS increase over the telemetry-off baseline** (~15 GiB mean / ~18–19 GiB peak on both sides),
|
||||
> with no OOM, no swap, and no leak across the run. The ~10 MB ceiling above is therefore a
|
||||
> provisioning safety margin (dominated by virtual thread-stack address space), not an expected
|
||||
> resident-memory increase. Steady-state cost shows up as throughput (~3–4% at head sampling 1.0),
|
||||
> not memory.
|
||||
|
||||
### 3.5.4 Performance Data Sources
|
||||
|
||||
The overhead estimates in Sections 3.3-3.5 are derived from the following sources:
|
||||
|
||||
| Source | What it covers | URL |
|
||||
| ------------------------------------------------ | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| OTel C++ SDK CI benchmarks (969 runs) | Span creation, context activation, sampler overhead | [Benchmark Dashboard](https://open-telemetry.github.io/opentelemetry-cpp/benchmarks/) |
|
||||
| `api/test/trace/span_benchmark.cc` | API-level span creation (~22 ns no-op) | [Source](https://github.com/open-telemetry/opentelemetry-cpp/blob/main/api/test/trace/span_benchmark.cc) |
|
||||
| `sdk/test/trace/sampler_benchmark.cc` | SDK span creation with samplers (~1,000 ns AlwaysOn) | [Source](https://github.com/open-telemetry/opentelemetry-cpp/blob/main/sdk/test/trace/sampler_benchmark.cc) |
|
||||
| `sdk/include/.../span_data.h` | SpanData memory layout (~250 bytes base) | [Source](https://github.com/open-telemetry/opentelemetry-cpp/blob/main/sdk/include/opentelemetry/sdk/trace/span_data.h) |
|
||||
| `sdk/src/trace/span.h` | Span wrapper memory layout (~88 bytes) | [Source](https://github.com/open-telemetry/opentelemetry-cpp/blob/main/sdk/src/trace/span.h) |
|
||||
| `sdk/include/.../batch_span_processor_options.h` | Default queue size (2048), batch size (512) | [Source](https://github.com/open-telemetry/opentelemetry-cpp/blob/main/sdk/include/opentelemetry/sdk/trace/batch_span_processor_options.h) |
|
||||
| `sdk/include/.../circular_buffer.h` | CircularBuffer implementation (AtomicUniquePtr array) | [Source](https://github.com/open-telemetry/opentelemetry-cpp/blob/main/sdk/include/opentelemetry/sdk/common/circular_buffer.h) |
|
||||
| OTLP proto definition | Serialized span size estimation | [Proto](https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/trace/v1/trace.proto) |
|
||||
|
||||
---
|
||||
|
||||
## 3.6 Network Overhead Analysis
|
||||
|
||||
### 3.6.1 Export Bandwidth
|
||||
|
||||
> **Bytes per span**: Estimates use ~500 bytes/span (conservative upper bound). OTLP protobuf analysis
|
||||
> shows a typical span with 3-5 string attributes serializes to ~200-300 bytes raw; with gzip
|
||||
> compression (~60-70% of raw) and batching (amortized headers), ~350 bytes/span is more realistic.
|
||||
> The table uses the conservative estimate for capacity planning.
|
||||
|
||||
| Sampling Rate | Spans/sec | Bandwidth | Notes |
|
||||
| ------------- | --------- | --------- | ---------------- |
|
||||
| 100% | ~500 | ~250 KB/s | Development only |
|
||||
| 10% | ~50 | ~25 KB/s | Staging |
|
||||
| 1% | ~5 | ~2.5 KB/s | Production |
|
||||
| Error-only | ~1 | ~0.5 KB/s | Minimal overhead |
|
||||
|
||||
### 3.6.2 Trace Context Propagation
|
||||
|
||||
| Message Type | Context Size | Messages/sec | Overhead |
|
||||
| ---------------------- | ------------ | ------------ | ----------- |
|
||||
| TMTransaction | 25 bytes | ~100 | ~2.5 KB/s |
|
||||
| TMProposeSet | 25 bytes | ~10 | ~250 B/s |
|
||||
| TMValidation | 25 bytes | ~50 | ~1.25 KB/s |
|
||||
| **Total P2P overhead** | | | **~4 KB/s** |
|
||||
|
||||
---
|
||||
|
||||
## 3.7 Optimization Strategies
|
||||
|
||||
### 3.7.1 Sampling Strategies
|
||||
|
||||
#### Tail Sampling
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
trace["New Trace"]
|
||||
|
||||
trace --> errors{"Is Error?"}
|
||||
errors -->|Yes| sample["SAMPLE"]
|
||||
errors -->|No| consensus{"Is Consensus?"}
|
||||
|
||||
consensus -->|Yes| sample
|
||||
consensus -->|No| slow{"Is Slow?"}
|
||||
|
||||
slow -->|Yes| sample
|
||||
slow -->|No| prob{"Random < 10%?"}
|
||||
|
||||
prob -->|Yes| sample
|
||||
prob -->|No| drop["DROP"]
|
||||
|
||||
style sample fill:#4caf50,stroke:#388e3c,color:#fff
|
||||
style drop fill:#f44336,stroke:#c62828,color:#fff
|
||||
```
|
||||
|
||||
### 3.7.2 Batch Tuning Recommendations
|
||||
|
||||
| Environment | Batch Size | Batch Delay | Max Queue |
|
||||
| ------------------ | ---------- | ----------- | --------- |
|
||||
| Low-latency | 128 | 1000ms | 512 |
|
||||
| High-throughput | 1024 | 10000ms | 8192 |
|
||||
| Memory-constrained | 256 | 2000ms | 512 |
|
||||
|
||||
### 3.7.3 Conditional Instrumentation
|
||||
|
||||
Instrumentation is gated on two levels. A compile-time feature flag (`XRPL_ENABLE_TELEMETRY`) reduces the trace macros to no-ops when telemetry is built out, so disabled builds carry zero cost. At runtime, per-component guards (e.g. `shouldTracePeer()`) skip span creation for components whose tracing is turned off, incurring no overhead beyond a single boolean check.
|
||||
|
||||
---
|
||||
|
||||
## 3.8 Links to Detailed Documentation
|
||||
|
||||
- **[Configuration Reference](./05-configuration-reference.md)**: Configuration options and collector setup
|
||||
- **[Implementation Phases](./06-implementation-phases.md)**: Detailed timeline and milestones
|
||||
|
||||
---
|
||||
|
||||
## 3.9 Code Intrusiveness Assessment
|
||||
|
||||
> **TxQ** = Transaction Queue
|
||||
|
||||
This section provides a detailed assessment of how intrusive the OpenTelemetry integration is to the existing xrpld codebase.
|
||||
|
||||
### 3.9.1 Files Modified Summary
|
||||
|
||||
| Component | Files Modified | Architectural Impact |
|
||||
| --------------------- | -------------- | -------------------- |
|
||||
| **Core Telemetry** | 10 new files | None (new module) |
|
||||
| **Application Init** | 2 files | Minimal |
|
||||
| **RPC Layer** | 3 files | Minimal |
|
||||
| **Transaction Relay** | 4 files | Low |
|
||||
| **Consensus** | 3 files | Low-Medium |
|
||||
| **Protocol Buffers** | 1 file | Low |
|
||||
| **CMake/Build** | 3 files | Minimal |
|
||||
| **PathFinding** | 2 | Minimal |
|
||||
| **TxQ/Fee** | 2 | Minimal |
|
||||
| **Validator/Amend** | 3 | Minimal |
|
||||
| **Total** | **~33 files** | **Low** |
|
||||
|
||||
### 3.9.2 Detailed File Impact
|
||||
|
||||
```mermaid
|
||||
pie title Code Changes by Component
|
||||
"New Telemetry Module" : 800
|
||||
"Transaction Relay" : 160
|
||||
"Consensus" : 130
|
||||
"RPC Layer" : 100
|
||||
"PathFinding" : 80
|
||||
"TxQ/Fee" : 60
|
||||
"Validator/Amendment" : 40
|
||||
"Application Init" : 35
|
||||
"Protocol Buffers" : 25
|
||||
"Build System" : 60
|
||||
```
|
||||
|
||||
#### New Files (No Impact on Existing Code)
|
||||
|
||||
| File | Purpose |
|
||||
| ------------------------------------------- | ------------------------- |
|
||||
| `include/xrpl/telemetry/Telemetry.h` | Main interface |
|
||||
| `include/xrpl/telemetry/TelemetryConfig.h` | Configuration structures |
|
||||
| `include/xrpl/telemetry/TraceContext.h` | Context propagation |
|
||||
| `include/xrpl/telemetry/SpanGuard.h` | RAII wrapper |
|
||||
| `include/xrpl/telemetry/DiscardFlag.h` | Thread-local discard flag |
|
||||
| `include/xrpl/telemetry/SpanAttributes.h` | Attribute helpers |
|
||||
| `src/libxrpl/telemetry/Telemetry.cpp` | Implementation |
|
||||
| `src/libxrpl/telemetry/TelemetryConfig.cpp` | Config parsing |
|
||||
| `src/libxrpl/telemetry/TraceContext.cpp` | Context serialization |
|
||||
| `src/libxrpl/telemetry/NullTelemetry.cpp` | No-op implementation |
|
||||
|
||||
#### Modified Files (Existing Xrpld Code)
|
||||
|
||||
| File | Risk Level |
|
||||
| ------------------------------------------------- | ---------- |
|
||||
| `src/xrpld/app/main/Application.cpp` | Low |
|
||||
| `include/xrpl/core/ServiceRegistry.h` | Low |
|
||||
| `src/xrpld/rpc/detail/ServerHandler.cpp` | Low |
|
||||
| `src/xrpld/rpc/handlers/*.cpp` | Low |
|
||||
| `src/xrpld/overlay/detail/PeerImp.cpp` | Medium |
|
||||
| `src/xrpld/overlay/detail/OverlayImpl.cpp` | Medium |
|
||||
| `src/xrpld/app/consensus/RCLConsensus.cpp` | Medium |
|
||||
| `src/xrpld/app/consensus/RCLConsensusAdaptor.cpp` | Medium |
|
||||
| `src/xrpld/core/JobQueue.cpp` | Low |
|
||||
| `src/xrpld/app/paths/PathRequest.cpp` | Low |
|
||||
| `src/xrpld/app/paths/Pathfinder.cpp` | Low |
|
||||
| `src/xrpld/app/misc/TxQ.cpp` | Low |
|
||||
| `src/xrpld/app/main/LoadManager.cpp` | Low |
|
||||
| `src/xrpld/app/misc/ValidatorList.cpp` | Low |
|
||||
| `src/xrpld/app/misc/AmendmentTable.cpp` | Low |
|
||||
| `src/xrpld/app/misc/Manifest.cpp` | Low |
|
||||
| `src/xrpld/shamap/SHAMap.cpp` | Low |
|
||||
| `src/xrpld/overlay/detail/ripple.proto` | Low |
|
||||
| `CMakeLists.txt` | Low |
|
||||
| `cmake/FindOpenTelemetry.cmake` | None (new) |
|
||||
|
||||
### 3.9.3 Risk Assessment by Component
|
||||
|
||||
<div align="center">
|
||||
|
||||
**Do First** ↖ ↗ **Plan Carefully**
|
||||
|
||||
```mermaid
|
||||
quadrantChart
|
||||
title Code Intrusiveness Risk Matrix
|
||||
x-axis Low Risk --> High Risk
|
||||
y-axis Low Value --> High Value
|
||||
|
||||
RPC Tracing: [0.2, 0.55]
|
||||
Transaction Relay: [0.55, 0.85]
|
||||
Consensus Tracing: [0.75, 0.92]
|
||||
Peer Message Tracing: [0.85, 0.35]
|
||||
JobQueue Context: [0.3, 0.42]
|
||||
Ledger Acquisition: [0.48, 0.65]
|
||||
PathFinding: [0.38, 0.72]
|
||||
TxQ and Fees: [0.25, 0.62]
|
||||
Validator Mgmt: [0.15, 0.35]
|
||||
```
|
||||
|
||||
**Optional** ↙ ↘ **Avoid**
|
||||
|
||||
</div>
|
||||
|
||||
#### Risk Level Definitions
|
||||
|
||||
| Risk Level | Definition | Mitigation |
|
||||
| ---------- | ---------------------------------------------------------------- | ---------------------------------- |
|
||||
| **Low** | Additive changes only; no modification to existing logic | Standard code review |
|
||||
| **Medium** | Minor modifications to existing functions; clear boundaries | Comprehensive unit tests |
|
||||
| **High** | Changes to core logic or data structures; potential side effects | Integration tests + staged rollout |
|
||||
|
||||
### 3.9.4 Architectural Impact Assessment
|
||||
|
||||
| Aspect | Impact | Justification |
|
||||
| -------------------- | ------- | -------------------------------------------------------------------------------- |
|
||||
| **Data Flow** | Minimal | Read-only instrumentation; no modification to consensus or transaction data flow |
|
||||
| **Threading Model** | Minimal | Context propagation uses thread-local storage (standard OTel pattern) |
|
||||
| **Memory Model** | Low | Bounded queues prevent unbounded growth; RAII ensures cleanup |
|
||||
| **Network Protocol** | Low | Optional fields in protobuf (high field numbers); backward compatible |
|
||||
| **Configuration** | None | New config section; existing configs unaffected |
|
||||
| **Build System** | Low | Optional CMake flag; builds work without OpenTelemetry |
|
||||
| **Dependencies** | Low | OpenTelemetry SDK is optional; null implementation when disabled |
|
||||
|
||||
### 3.9.5 Backward Compatibility
|
||||
|
||||
| Compatibility | Status | Notes |
|
||||
| --------------- | ------- | ----------------------------------------------------- |
|
||||
| **Config File** | ✅ Full | New `[telemetry]` section is optional |
|
||||
| **Protocol** | ✅ Full | Optional protobuf fields with high field numbers |
|
||||
| **Build** | ✅ Full | `XRPL_ENABLE_TELEMETRY=OFF` produces identical binary |
|
||||
| **Runtime** | ✅ Full | `enabled=0` produces zero overhead |
|
||||
| **API** | ✅ Full | No changes to public RPC or P2P APIs |
|
||||
|
||||
### 3.9.6 Rollback Strategy
|
||||
|
||||
If issues are discovered after deployment:
|
||||
|
||||
1. **Immediate**: Set `enabled=0` in config and restart (zero code change)
|
||||
2. **Quick**: Rebuild with `XRPL_ENABLE_TELEMETRY=OFF`
|
||||
3. **Complete**: Revert telemetry commits (clean separation makes this easy)
|
||||
|
||||
### 3.9.7 Code Change Examples
|
||||
|
||||
**Minimal RPC Instrumentation (Low Intrusiveness):** Instrumenting an RPC handler adds roughly 3-4 lines: one macro to start the span and one or two `setAttribute` calls (command name, status). The span ends automatically via RAII, so the existing control flow — process the request, send the result — is untouched.
|
||||
|
||||
**Consensus Instrumentation (Medium Intrusiveness):** Consensus is slightly more intrusive because child spans in later phase transitions need the round's context. Beyond the span-start and attribute macros, this requires storing the active context in a new member variable (`currentRoundContext_`) at round start. The existing round logic itself remains unchanged.
|
||||
|
||||
---
|
||||
|
||||
_Previous: [Design Decisions](./02-design-decisions.md)_ | _Next: [Configuration Reference](./05-configuration-reference.md)_ | _Back to: [Overview](./OpenTelemetryPlan.md)_
|
||||
275
OpenTelemetryPlan/05-configuration-reference.md
Normal file
275
OpenTelemetryPlan/05-configuration-reference.md
Normal file
@@ -0,0 +1,275 @@
|
||||
# Configuration Reference
|
||||
|
||||
> **Parent Document**: [OpenTelemetryPlan.md](./OpenTelemetryPlan.md)
|
||||
> **Related**: [Implementation Phases](./06-implementation-phases.md)
|
||||
|
||||
---
|
||||
|
||||
## 5.1 xrpld Configuration
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol | **TxQ** = Transaction Queue
|
||||
|
||||
### 5.1.1 Configuration File Section
|
||||
|
||||
The authoritative `[telemetry]` example lives in `cfg/xrpld-example.cfg`. Telemetry is disabled by default (`enabled=0`); enabling it turns on distributed tracing for transaction flow, consensus, and RPC calls, with traces exported to an OpenTelemetry Collector over OTLP. Head sampling is intentionally fixed at 1.0 (sample everything) and is not configurable — per-node head-sampling would produce broken/partial distributed traces, so volume reduction is delegated to the collector's tail sampling (see Section 7.4.2). The full option reference follows.
|
||||
|
||||
### 5.1.2 Configuration Options Summary
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
| -------------------------- | ------ | --------------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
||||
| `enabled` | bool | `false` | Enable/disable telemetry |
|
||||
| `endpoint` | string | `http://localhost:4318/v1/traces` | OTLP/HTTP collector endpoint |
|
||||
| `use_tls` | bool | `false` | Enable TLS for exporter connection |
|
||||
| `tls_ca_cert` | string | `""` | Path to CA certificate file |
|
||||
| `tls_client_cert` | string | `""` | Path to node's client certificate (PEM) for mutual TLS; empty = one-way TLS |
|
||||
| `tls_client_key` | string | `""` | Path to private key (PEM) for `tls_client_cert`; required when it is set |
|
||||
| `batch_size` | uint | `512` | Spans per export batch |
|
||||
| `batch_delay_ms` | uint | `5000` | Max delay before sending batch (ms) |
|
||||
| `max_queue_size` | uint | `2048` | Maximum queued spans |
|
||||
| `trace_transactions` | bool | `true` | Enable transaction tracing |
|
||||
| `trace_consensus` | bool | `true` | Enable consensus tracing |
|
||||
| `trace_rpc` | bool | `true` | Enable RPC tracing |
|
||||
| `trace_peer` | bool | `true` | Enable peer message tracing (high volume) |
|
||||
| `trace_ledger` | bool | `true` | Enable ledger tracing |
|
||||
| `tx_trace_strategy` | string | `"deterministic"` | TX trace ID strategy: `"deterministic"` (trace_id = txHash[0:16]) or `"attribute"` (random) |
|
||||
| `consensus_trace_strategy` | string | `"deterministic"` | Consensus trace ID strategy: `"deterministic"` (trace_id = prevLedgerHash[0:16]) or `"attribute"` (random) |
|
||||
| `service_name` | string | `"xrpld"` | Service name (`service.name`) for traces and metrics |
|
||||
| `service_instance_id` | string | `<node_pubkey>` | Instance identifier |
|
||||
|
||||
**Planned (not yet implemented)**: the following options appear in the design
|
||||
documents but are not parsed by `TelemetryConfig.cpp` in Phase 1b and later
|
||||
phases. They will be added as the corresponding subsystems are instrumented:
|
||||
|
||||
| Option | Planned Phase | Purpose |
|
||||
| -------------------------- | ------------- | ----------------------------------------------------------------------- |
|
||||
| `exporter` | Future | Select between OTLP/HTTP and OTLP/gRPC |
|
||||
| `trace_pathfind` | Phase 2 | Path computation tracing toggle |
|
||||
| `trace_txq` | Phase 3 | Transaction queue tracing toggle |
|
||||
| `trace_validator` | Future | Validator list / manifest update tracing |
|
||||
| `trace_amendment` | Future | Amendment voting tracing |
|
||||
| `consensus_trace_strategy` | Phase 4 | Trace ID strategy for consensus rounds (`deterministic` \| `attribute`) |
|
||||
|
||||
---
|
||||
|
||||
## 5.2 Configuration Parser
|
||||
|
||||
> **TxQ** = Transaction Queue
|
||||
|
||||
The parser `setupTelemetry()` in `src/libxrpl/telemetry/TelemetryConfig.cpp` reads the `[telemetry]` `Section` and populates a `Telemetry::Setup` struct, applying the defaults listed in Section 5.1.2 via `section.value_or(...)`. It derives `serviceInstanceId` from the node public key when not overridden, selects the exporter endpoint default by exporter type, and leaves the sampling ratio at its fixed 1.0 default (not read from config — see Section 7.4.2).
|
||||
|
||||
---
|
||||
|
||||
## 5.3 Application Integration
|
||||
|
||||
### 5.3.1 ApplicationImp Changes
|
||||
|
||||
> **Deferred identity**: The node public key (`nodeIdentity_`) is not
|
||||
> available during `ApplicationImp`'s member initializer list — it is
|
||||
> resolved later in `setup()`. The `Telemetry` object is therefore
|
||||
> constructed with an empty `serviceInstanceId` and patched via
|
||||
> `setServiceInstanceId()` once `setup()` has called `getNodeIdentity()`.
|
||||
|
||||
`ApplicationImp` (in `src/xrpld/app/main/Application.cpp`) owns a `std::unique_ptr<telemetry::Telemetry> telemetry_`. It is built in the member initializer list via `makeTelemetry(setupTelemetry(...))` with an empty `serviceInstanceId`, then patched in `setup()` by calling `setServiceInstanceId()` with the Base58 node public key (unless the user supplied a custom `service_instance_id`). `start()` and `run()` forward to `telemetry_->start()` / `telemetry_->stop()`, and `getTelemetry()` returns the owned instance.
|
||||
|
||||
### 5.3.2 ServiceRegistry Interface Addition
|
||||
|
||||
`include/xrpl/core/ServiceRegistry.h` gains a pure-virtual `telemetry::Telemetry& getTelemetry()` (with a forward declaration of `telemetry::Telemetry`), giving every component a uniform accessor for the tracing subsystem.
|
||||
|
||||
> **Note:** `Application` extends `ServiceRegistry`, so `getTelemetry()` is
|
||||
> available on both. Components that hold a `ServiceRegistry&` (e.g.
|
||||
> `NetworkOPsImp`) call `registry_.get().getTelemetry()`. Components that
|
||||
> still hold an `Application&` (e.g. `ServerHandler`, `PeerImp`,
|
||||
> `RCLConsensusAdaptor`) call `app_.getTelemetry()` directly.
|
||||
|
||||
---
|
||||
|
||||
## 5.4 CMake Integration
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
### 5.4.1 Find OpenTelemetry Module
|
||||
|
||||
A `cmake/FindOpenTelemetry.cmake` module locates the OpenTelemetry C++ SDK. It first tries `find_package(opentelemetry-cpp CONFIG)`, aliasing the imported targets `OpenTelemetry::api`, `OpenTelemetry::sdk`, and `OpenTelemetry::otlp_grpc_exporter`, and falls back to `pkg-config` when no CMake config package is present.
|
||||
|
||||
### 5.4.2 CMakeLists.txt Changes
|
||||
|
||||
The top-level `CMakeLists.txt` adds an `XRPL_ENABLE_TELEMETRY` option (default `OFF`). When enabled, it runs `find_package(OpenTelemetry REQUIRED)`, defines the `XRPL_ENABLE_TELEMETRY` compile flag, and builds the `xrpl_telemetry` library from the real telemetry sources linked against the OpenTelemetry targets; when disabled, it builds the same target from a no-op `NullTelemetry.cpp` so call sites compile unchanged.
|
||||
|
||||
---
|
||||
|
||||
## 5.5 OpenTelemetry Collector Configuration
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol | **APM** = Application Performance Monitoring
|
||||
|
||||
> **Production hardening**: The configurations in this section are starting points. For production deployments where xrpld ships telemetry across a network to a centrally-hosted collector, see [Securing the OTel Pipeline](./secure-OTel.md) for the required mTLS receiver config, NetworkPolicy, and peer trace-context validation.
|
||||
|
||||
The authoritative collector config lives in the repo at `docker/telemetry/otel-collector-config.yaml` (with Tempo backend config in `docker/telemetry/tempo.yaml`). The sections below summarize the development and production shapes of that pipeline.
|
||||
|
||||
### 5.5.1 Development Configuration
|
||||
|
||||
The development collector enables an OTLP receiver on both gRPC (`0.0.0.0:4317`) and HTTP (`0.0.0.0:4318`), a single `batch` processor (1s timeout, batch size 100), and two exporters: a `logging` exporter for console debugging and `otlp/tempo` (insecure) for trace visualization. The single `traces` pipeline wires receiver → batch → both exporters.
|
||||
|
||||
### 5.5.2 Production Configuration
|
||||
|
||||
The production collector adds TLS on the OTLP gRPC receiver and a richer processor chain: a `memory_limiter` (OOM guard), `batch` (5s timeout, size 512), `tail_sampling`, and an `attributes` processor that hashes sensitive fields (e.g. `tx_account`) and stamps `deployment.environment`. Tail sampling keeps all `ERROR` traces, slow consensus rounds (>5s) and slow RPC requests (>1s), and probabilistically samples the remainder at 10%. Exporters target Grafana Tempo (TLS) and Elastic APM; `health_check` and `zpages` extensions are enabled for operability.
|
||||
|
||||
---
|
||||
|
||||
## 5.6 Docker Compose Development Environment
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
The authoritative development stack lives in the repo at `docker/telemetry/docker-compose.yml`. It brings up four services on a shared `xrpld-telemetry` network: an `otel-collector` (otel/opentelemetry-collector-contrib) exposing OTLP gRPC `4317`, OTLP HTTP `4318`, and health check `13133`; `tempo` for trace storage/visualization; `grafana` with provisioned datasources and dashboards (anonymous admin enabled); and an optional `prometheus` for metric correlation.
|
||||
|
||||
---
|
||||
|
||||
## 5.7 Configuration Architecture
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph config["Configuration Sources"]
|
||||
cfgFile["xrpld.cfg<br/>[telemetry] section"]
|
||||
cmake["CMake<br/>XRPL_ENABLE_TELEMETRY"]
|
||||
end
|
||||
|
||||
subgraph init["Initialization"]
|
||||
parse["setupTelemetry()"]
|
||||
factory["makeTelemetry()"]
|
||||
end
|
||||
|
||||
subgraph runtime["Runtime Components"]
|
||||
tracer["TracerProvider"]
|
||||
exporter["OTLP Exporter"]
|
||||
processor["BatchProcessor"]
|
||||
end
|
||||
|
||||
subgraph collector["Collector Pipeline"]
|
||||
recv["Receivers"]
|
||||
proc["Processors"]
|
||||
exp["Exporters"]
|
||||
end
|
||||
|
||||
cfgFile --> parse
|
||||
cmake -->|"compile flag"| parse
|
||||
parse --> factory
|
||||
factory --> tracer
|
||||
tracer --> processor
|
||||
processor --> exporter
|
||||
exporter -->|"OTLP"| recv
|
||||
recv --> proc
|
||||
proc --> exp
|
||||
|
||||
style config fill:#e3f2fd,stroke:#1976d2
|
||||
style runtime fill:#e8f5e9,stroke:#388e3c
|
||||
style collector fill:#fff3e0,stroke:#ff9800
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **Configuration Sources**: `xrpld.cfg` provides runtime settings (endpoint, sampling) while the CMake flag controls whether telemetry is compiled in at all.
|
||||
- **Initialization**: `setupTelemetry()` parses config values, then `makeTelemetry()` constructs the provider, processor, and exporter objects.
|
||||
- **Runtime Components**: The `TracerProvider` creates spans, the `BatchProcessor` buffers them, and the `OTLP Exporter` serializes and sends them over the wire.
|
||||
- **OTLP arrow to Collector**: Trace data leaves the xrpld process via OTLP (gRPC or HTTP) and enters the external Collector pipeline.
|
||||
- **Collector Pipeline**: `Receivers` ingest OTLP data, `Processors` apply sampling/filtering/enrichment, and `Exporters` forward traces to storage backends (Tempo, etc.).
|
||||
|
||||
---
|
||||
|
||||
## 5.8 Grafana Integration
|
||||
|
||||
> **APM** = Application Performance Monitoring
|
||||
|
||||
Step-by-step instructions for integrating xrpld traces with Grafana.
|
||||
|
||||
### 5.8.1 Data Source Configuration
|
||||
|
||||
#### Tempo (Recommended)
|
||||
|
||||
A Tempo datasource (`grafana/provisioning/datasources/tempo.yaml`, provisioned from `docker/telemetry/grafana/`) points at `http://tempo:3200` and enables `tracesToLogs` (linking to Loki on `service.name`/`tx_hash` and mapping `trace_id` → `traceID`), `serviceMap` against Prometheus, the node graph, and Loki search.
|
||||
|
||||
#### Elastic APM
|
||||
|
||||
Alternatively, an Elasticsearch datasource (`grafana/provisioning/datasources/elastic-apm.yaml`) of type `elasticsearch` points at `http://elasticsearch:9200` against the `apm-*` index, using `@timestamp` as the time field and mapping the log message/level fields.
|
||||
|
||||
### 5.8.2 Dashboard Provisioning
|
||||
|
||||
A dashboard provider (`grafana/provisioning/dashboards/dashboards.yaml`) loads the `xrpld` dashboard folder from disk (`/var/lib/grafana/dashboards/rippled`), polling for changes every 30s with deletion disabled.
|
||||
|
||||
### 5.8.3 Example Dashboard: RPC Performance
|
||||
|
||||
An example `xrpld RPC Performance` dashboard (uid `xrpld-rpc-performance`) sourced from Tempo via TraceQL provides four panels: RPC latency by command (heatmap), RPC error rate by command (timeseries), the top 10 slowest RPC commands by average duration (table), and a recent-traces table.
|
||||
|
||||
### 5.8.4 Example Dashboard: Transaction Tracing
|
||||
|
||||
An example `xrpld Transaction Tracing` dashboard (uid `xrpld-tx-tracing`) over Tempo provides three panels: transaction throughput (`tx.receive` rate, stat), cross-node relay count (average `span.relay_count` on `tx.relay`, timeseries), and a table of transaction validation errors (`tx.validate` with `status.code=error`).
|
||||
|
||||
### 5.8.5 TraceQL Query Examples
|
||||
|
||||
Common queries for xrpld traces:
|
||||
|
||||
```
|
||||
# Find all traces for a specific transaction hash
|
||||
{resource.service.name="xrpld" && span.tx_hash="ABC123..."}
|
||||
|
||||
# Find slow RPC commands (>100ms)
|
||||
{resource.service.name="xrpld" && name=~"rpc.command.*"} | duration > 100ms
|
||||
|
||||
# Find consensus rounds taking >5 seconds
|
||||
{resource.service.name="xrpld" && name="consensus.round"} | duration > 5s
|
||||
|
||||
# Find failed transactions with error details
|
||||
{resource.service.name="xrpld" && name="tx.validate" && status.code=error}
|
||||
|
||||
# Find transactions relayed to many peers
|
||||
{resource.service.name="xrpld" && name="tx.relay"} | span.relay_count > 10
|
||||
|
||||
# Compare latency across nodes
|
||||
{resource.service.name="xrpld" && name="rpc.command.account_info"} | avg(duration) by (resource.service.instance.id)
|
||||
```
|
||||
|
||||
### 5.8.6 Correlation with PerfLog
|
||||
|
||||
To correlate OpenTelemetry traces with existing PerfLog data:
|
||||
|
||||
**Step 1: Configure Loki to ingest PerfLog**
|
||||
|
||||
Configure a Promtail scrape job (`promtail-config.yaml`) that tails `/var/log/rippled/perf*.log`, parses each JSON line, and promotes `trace_id`, `ledger_seq`, and `tx_hash` to Loki labels.
|
||||
|
||||
**Step 2: Add trace_id to PerfLog entries**
|
||||
|
||||
Modify PerfLog so its JSON output includes a `trace_id` field whenever a valid span is active: fetch the current span from the OpenTelemetry runtime context, and if its context is valid, render the trace ID as a 32-character lowercase hex string into the log entry.
|
||||
|
||||
**Step 3: Configure Grafana trace-to-logs link**
|
||||
|
||||
In the Tempo datasource, set the `tracesToLogs` derived field to link to Loki on the `trace_id` and `tx_hash` tags, with `filterByTraceID: true`.
|
||||
|
||||
### 5.8.7 Correlation with Insight/OTel System Metrics
|
||||
|
||||
To correlate traces with Beast Insight system metrics:
|
||||
|
||||
**Step 1: Export Insight metrics to Prometheus**
|
||||
|
||||
Beast Insight metrics are exported natively via OTLP to the OTel Collector,
|
||||
which exposes them on the Prometheus endpoint alongside spanmetrics. Configure
|
||||
the `[insight]` section of `xrpld.cfg` with `server=otel`,
|
||||
`endpoint=http://localhost:4318/v1/metrics`, and `prefix=xrpld`; no separate
|
||||
StatsD exporter or Prometheus scrape job is needed when using `server=otel`.
|
||||
|
||||
**Step 2: Add exemplars to metrics**
|
||||
|
||||
The OpenTelemetry SDK automatically adds exemplars (trace IDs) to metrics when using the Prometheus exporter, linking metric spikes to specific traces.
|
||||
|
||||
**Step 3: Configure Grafana metric-to-trace link**
|
||||
|
||||
In the Prometheus datasource, set `exemplarTraceIdDestinations` to map the `trace_id` exemplar to the Tempo datasource.
|
||||
|
||||
**Step 4: Dashboard panel with exemplars**
|
||||
|
||||
Add a timeseries panel over Prometheus (e.g. `histogram_quantile(0.99, rate(xrpld_rpc_duration_seconds_bucket[5m]))`) with `exemplar: true` enabled.
|
||||
|
||||
This allows clicking on metric data points to jump directly to the related trace.
|
||||
|
||||
---
|
||||
|
||||
_Previous: [Implementation Strategy](./03-implementation-strategy.md)_ | _Next: [Implementation Phases](./06-implementation-phases.md)_ | _Back to: [Overview](./OpenTelemetryPlan.md)_
|
||||
1546
OpenTelemetryPlan/06-implementation-phases.md
Normal file
1546
OpenTelemetryPlan/06-implementation-phases.md
Normal file
File diff suppressed because it is too large
Load Diff
407
OpenTelemetryPlan/07-observability-backends.md
Normal file
407
OpenTelemetryPlan/07-observability-backends.md
Normal file
@@ -0,0 +1,407 @@
|
||||
# Observability Backend Recommendations
|
||||
|
||||
> **Parent Document**: [OpenTelemetryPlan.md](./OpenTelemetryPlan.md)
|
||||
> **Related**: [Implementation Phases](./06-implementation-phases.md) | [Appendix](./08-appendix.md)
|
||||
|
||||
---
|
||||
|
||||
## 7.1 Development/Testing Backends
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
| Backend | Pros | Cons | Use Case |
|
||||
| ---------- | ----------------------------------- | ---------------------- | ------------------- |
|
||||
| **Tempo** | Cost-effective, Grafana integration | Requires Grafana stack | Local dev, CI, Prod |
|
||||
| **Zipkin** | Simple, lightweight | Basic features | Quick prototyping |
|
||||
|
||||
### Quick Start with Tempo
|
||||
|
||||
```bash
|
||||
# Start Tempo with OTLP support
|
||||
docker run -d --name tempo \
|
||||
-p 3200:3200 \
|
||||
-p 4317:4317 \
|
||||
-p 4318:4318 \
|
||||
grafana/tempo:2.6.1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7.2 Production Backends
|
||||
|
||||
> **APM** = Application Performance Monitoring
|
||||
|
||||
| Backend | Pros | Cons | Use Case |
|
||||
| ----------------- | ----------------------------------------- | ---------------------- | --------------------------- |
|
||||
| **Grafana Tempo** | Cost-effective, Grafana integration | Requires Grafana stack | Most production deployments |
|
||||
| **Elastic APM** | Full observability stack, log correlation | Resource intensive | Existing Elastic users |
|
||||
| **Honeycomb** | Excellent query, high cardinality | SaaS cost | Deep debugging needs |
|
||||
| **Datadog APM** | Full platform, easy setup | SaaS cost | Enterprise with budget |
|
||||
|
||||
### Backend Selection Flowchart
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
start[Select Backend] --> budget{Budget<br/>Constraints?}
|
||||
|
||||
budget -->|Yes| oss[Open Source]
|
||||
budget -->|No| saas{Prefer<br/>SaaS?}
|
||||
|
||||
oss --> existing{Existing<br/>Stack?}
|
||||
existing -->|Grafana| tempo[Grafana Tempo]
|
||||
existing -->|Elastic| elastic[Elastic APM]
|
||||
existing -->|None| tempo
|
||||
|
||||
saas -->|Yes| enterprise{Enterprise<br/>Support?}
|
||||
saas -->|No| oss
|
||||
|
||||
enterprise -->|Yes| datadog[Datadog APM]
|
||||
enterprise -->|No| honeycomb[Honeycomb]
|
||||
|
||||
tempo --> final[Configure Collector]
|
||||
elastic --> final
|
||||
honeycomb --> final
|
||||
datadog --> final
|
||||
|
||||
style start fill:#0f172a,stroke:#020617,color:#fff
|
||||
style budget fill:#334155,stroke:#1e293b,color:#fff
|
||||
style oss fill:#1e293b,stroke:#0f172a,color:#fff
|
||||
style existing fill:#334155,stroke:#1e293b,color:#fff
|
||||
style saas fill:#334155,stroke:#1e293b,color:#fff
|
||||
style enterprise fill:#334155,stroke:#1e293b,color:#fff
|
||||
style final fill:#0f172a,stroke:#020617,color:#fff
|
||||
style tempo fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style elastic fill:#bf360c,stroke:#8c2809,color:#fff
|
||||
style honeycomb fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style datadog fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **Budget Constraints? (Yes)**: Leads to open-source options. If you already run Grafana or Elastic, pick the matching backend; otherwise default to Grafana Tempo.
|
||||
- **Budget Constraints? (No) → Prefer SaaS?**: If you want a managed service, choose between Datadog (enterprise support) and Honeycomb (developer-focused). If not, fall back to open-source.
|
||||
- **Terminal nodes (Tempo / Elastic / Honeycomb / Datadog)**: Each represents a concrete backend choice, all of which feed into the same final step.
|
||||
- **Configure Collector**: Regardless of backend, you always finish by configuring the OTel Collector to export to your chosen destination.
|
||||
|
||||
---
|
||||
|
||||
## 7.3 Recommended Production Architecture
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol | **APM** = Application Performance Monitoring | **HA** = High Availability
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph validators["Validator Nodes"]
|
||||
v1[xrpld<br/>Validator 1]
|
||||
v2[xrpld<br/>Validator 2]
|
||||
end
|
||||
|
||||
subgraph stock["Stock Nodes"]
|
||||
s1[xrpld<br/>Stock 1]
|
||||
s2[xrpld<br/>Stock 2]
|
||||
end
|
||||
|
||||
subgraph collector["OTel Collector Cluster"]
|
||||
c1[Collector<br/>DC1]
|
||||
c2[Collector<br/>DC2]
|
||||
end
|
||||
|
||||
subgraph backends["Storage Backends"]
|
||||
tempo[(Grafana<br/>Tempo)]
|
||||
elastic[(Elastic<br/>APM)]
|
||||
archive[(S3/GCS<br/>Archive)]
|
||||
end
|
||||
|
||||
subgraph ui["Visualization"]
|
||||
grafana[Grafana<br/>Dashboards]
|
||||
end
|
||||
|
||||
v1 -->|OTLP| c1
|
||||
v2 -->|OTLP| c1
|
||||
s1 -->|OTLP| c2
|
||||
s2 -->|OTLP| c2
|
||||
|
||||
c1 --> tempo
|
||||
c1 --> elastic
|
||||
c2 --> tempo
|
||||
c2 --> archive
|
||||
|
||||
tempo --> grafana
|
||||
elastic --> grafana
|
||||
|
||||
%% Note: simplified single-collector-per-DC topology shown for clarity
|
||||
|
||||
style validators fill:#b71c1c,stroke:#7f1d1d,color:#ffffff
|
||||
style stock fill:#0d47a1,stroke:#082f6a,color:#ffffff
|
||||
style collector fill:#bf360c,stroke:#8c2809,color:#ffffff
|
||||
style backends fill:#1b5e20,stroke:#0d3d14,color:#ffffff
|
||||
style ui fill:#4a148c,stroke:#2e0d57,color:#ffffff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **Validator / Stock Nodes**: All xrpld nodes emit trace data via OTLP. Validators and stock nodes are grouped separately because they may reside in different network zones.
|
||||
- **Collector Cluster (DC1, DC2)**: Regional collectors receive OTLP from nodes in their datacenter, apply processing (sampling, enrichment), and fan out to multiple backends. Enrichment includes deployment-tier tagging: each collector stamps `deployment.environment` and (as a fallback) `xrpl.network.type` so one Grafana stack can filter data from many collectors by tier.
|
||||
- **Storage Backends**: Tempo and Elastic provide queryable trace storage; S3/GCS Archive provides long-term cold storage for compliance or post-incident analysis.
|
||||
- **Grafana Dashboards**: The single visualization layer that queries both Tempo and Elastic, giving operators a unified view of all traces.
|
||||
- **Data flow direction**: Nodes → Collectors → Storage → Grafana. Each arrow represents a network hop; minimizing collector-to-backend hops reduces latency.
|
||||
|
||||
> **Note**: Production deployments should use multiple collector instances behind a load balancer for high availability. The diagram shows a simplified single-collector topology for clarity.
|
||||
|
||||
---
|
||||
|
||||
## 7.4 Architecture Considerations
|
||||
|
||||
### 7.4.1 Collector Placement
|
||||
|
||||
| Strategy | Description | Pros | Cons |
|
||||
| ------------- | -------------------- | ------------------------ | ----------------------- |
|
||||
| **Sidecar** | Collector per node | Isolation, simple config | Resource overhead |
|
||||
| **DaemonSet** | Collector per host | Shared resources | Complexity |
|
||||
| **Gateway** | Central collector(s) | Centralized processing | Single point of failure |
|
||||
|
||||
**Recommendation**: Use **Gateway** pattern with regional collectors for xrpld networks:
|
||||
|
||||
- One collector cluster per datacenter/region
|
||||
- Tail-based sampling at collector level
|
||||
- Multiple export destinations for redundancy
|
||||
|
||||
### 7.4.2 Sampling Strategy
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph head["Head Sampling (Node)"]
|
||||
hs[Node-level head sampling<br/>fixed at 100%<br/>not configurable]
|
||||
end
|
||||
|
||||
subgraph tail["Tail Sampling (Collector)"]
|
||||
ts1[Keep all errors]
|
||||
ts2[Keep slow >5s]
|
||||
ts3[Keep 10% rest]
|
||||
end
|
||||
|
||||
head --> tail
|
||||
|
||||
ts1 --> final[Final Traces]
|
||||
ts2 --> final
|
||||
ts3 --> final
|
||||
|
||||
style head fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style tail fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style hs fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style ts1 fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style ts2 fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style ts3 fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style final fill:#bf360c,stroke:#8c2809,color:#fff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **Head Sampling (Node)**: xrpld pins head sampling at 100% (sample everything) and does not expose a configurable ratio. This is intentional: a per-node ratio would let different nodes make divergent keep/drop decisions for the same distributed trace, producing broken/partial traces. xrpld uses a `ParentBased` sampler so spans inheriting a remote parent honor the upstream decision. Volume reduction is delegated to the collector's tail sampling.
|
||||
- **Tail Sampling (Collector)**: The second filter -- the collector inspects completed traces and applies rules: keep all errors, keep anything slower than 5 seconds, and keep 10% of the remainder.
|
||||
- **Arrow head → tail**: All head-sampled traces flow to the collector, where tail sampling further reduces volume while preserving the most valuable data.
|
||||
- **Final Traces**: The output after both sampling stages; this is what gets stored and queried. The two-stage approach balances cost with debuggability.
|
||||
|
||||
### 7.4.3 Data Retention
|
||||
|
||||
| Environment | Hot Storage | Warm Storage | Cold Archive |
|
||||
| ----------- | ----------- | ------------ | ------------ |
|
||||
| Development | 24 hours | N/A | N/A |
|
||||
| Staging | 7 days | N/A | N/A |
|
||||
| Production | 7 days | 30 days | many years |
|
||||
|
||||
---
|
||||
|
||||
## 7.5 Integration Checklist
|
||||
|
||||
- [ ] Choose primary backend (Tempo recommended for cost/features)
|
||||
- [ ] Deploy collector cluster with high availability
|
||||
- [ ] Configure tail-based sampling for error/latency traces
|
||||
- [ ] Set up Grafana dashboards for trace visualization
|
||||
- [ ] Configure alerts for trace anomalies
|
||||
- [ ] Establish data retention policies
|
||||
- [ ] Test trace correlation with logs and metrics
|
||||
|
||||
---
|
||||
|
||||
## 7.6 Grafana Dashboard Examples
|
||||
|
||||
Pre-built dashboards for xrpld observability.
|
||||
|
||||
### 7.6.1 Consensus Health Dashboard
|
||||
|
||||
A Tempo-backed dashboard (uid `xrpld-consensus-health`) with four panels, all driven by TraceQL:
|
||||
|
||||
- **Consensus Round Duration** (timeseries, ms): average `consensus.round` span duration per node instance, with yellow/red thresholds at 4s/5s.
|
||||
- **Phase Duration Breakdown** (barchart): average duration of `consensus.phase.*` spans grouped by span name.
|
||||
- **Proposers per Round** (stat): average of the `span.proposers` attribute on `consensus.round` spans.
|
||||
- **Recent Slow Rounds (>5s)** (table): `consensus.round` spans filtered to `duration > 5s`.
|
||||
|
||||
The underlying TraceQL queries are listed in section 7.7.3 and used throughout this doc.
|
||||
|
||||
### 7.6.2 Node Overview Dashboard
|
||||
|
||||
A Tempo-backed dashboard (uid `xrpld-node-overview`) with four panels:
|
||||
|
||||
- **Active Nodes** (stat): count of distinct `resource.service.instance.id` values seen for the `xrpld` service.
|
||||
- **Total Transactions (1h)** (stat): count of `tx.receive` spans.
|
||||
- **Error Rate** (gauge, percent): ratio of `status.code=error` spans to all spans, with yellow/red thresholds at 1%/5%.
|
||||
- **Service Map** (nodeGraph): Tempo-generated service dependency graph.
|
||||
|
||||
### 7.6.3 Alert Rules
|
||||
|
||||
Grafana provisions three TraceQL-based alert rules (group `xrpld-tracing-alerts`, evaluated every 1m) against the Tempo datasource:
|
||||
|
||||
- **Consensus Round Slow** (warning, `for: 5m`): fires when average `consensus.round` duration exceeds 5s.
|
||||
|
||||
```
|
||||
{resource.service.name="xrpld" && name="consensus.round"} | avg(duration) > 5s
|
||||
```
|
||||
|
||||
- **RPC Error Rate Spike** (critical, `for: 2m`): fires when the error rate across `rpc.command.*` spans exceeds 5%. Error _rate_ is a ratio, so it must divide the error-span rate by the total-span rate — a single TraceQL `rate()` returns spans/second, not a percentage, and would fire on traffic volume alone. This uses span metrics emitted by the collector's `spanmetrics` connector (Prometheus datasource), not a TraceQL query:
|
||||
|
||||
```
|
||||
sum(rate(calls_total{service_name="xrpld", span_name=~"rpc.command.*", status_code="STATUS_CODE_ERROR"}[5m]))
|
||||
/
|
||||
sum(rate(calls_total{service_name="xrpld", span_name=~"rpc.command.*"}[5m]))
|
||||
> 0.05
|
||||
```
|
||||
|
||||
- **Transaction Throughput Drop** (warning, `for: 10m`): fires when the `tx.receive` span rate falls below 10/s.
|
||||
|
||||
```
|
||||
{resource.service.name="xrpld" && name="tx.receive"} | rate() < 10
|
||||
```
|
||||
|
||||
> **Note**: The Consensus Round Slow and Transaction Throughput Drop rules use TraceQL aggregates (`avg(duration)`, `rate()`), which require Tempo 2.3+ with TraceQL metrics enabled. Verify aggregate query support in your Tempo version before provisioning. The RPC Error Rate Spike rule instead queries Prometheus span metrics (collector `spanmetrics` connector), so it needs that connector enabled in the collector pipeline.
|
||||
|
||||
---
|
||||
|
||||
## 7.7 PerfLog and Insight Correlation
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
How to correlate OpenTelemetry traces with existing xrpld observability.
|
||||
|
||||
### 7.7.1 Correlation Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph xrpld["xrpld Node"]
|
||||
otel[OpenTelemetry<br/>Spans]
|
||||
perflog[PerfLog<br/>JSON Logs]
|
||||
insight[Beast Insight<br/>StatsD Metrics]
|
||||
end
|
||||
|
||||
subgraph collectors["Data Collection"]
|
||||
otelc[OTel Collector]
|
||||
promtail[Promtail/Fluentd]
|
||||
statsd[StatsD Exporter]
|
||||
end
|
||||
|
||||
subgraph storage["Storage"]
|
||||
tempo[(Tempo)]
|
||||
loki[(Loki)]
|
||||
prom[(Prometheus)]
|
||||
end
|
||||
|
||||
subgraph grafana["Grafana"]
|
||||
traces[Trace View]
|
||||
logs[Log View]
|
||||
metrics[Metrics View]
|
||||
corr[Correlation<br/>Panel]
|
||||
end
|
||||
|
||||
otel -->|OTLP| otelc --> tempo
|
||||
perflog -->|JSON| promtail --> loki
|
||||
insight -->|StatsD| statsd --> prom
|
||||
|
||||
tempo --> traces
|
||||
loki --> logs
|
||||
prom --> metrics
|
||||
|
||||
traces --> corr
|
||||
logs --> corr
|
||||
metrics --> corr
|
||||
|
||||
style xrpld fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style collectors fill:#bf360c,stroke:#8c2809,color:#fff
|
||||
style storage fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style grafana fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
style otel fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style perflog fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style insight fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style otelc fill:#bf360c,stroke:#8c2809,color:#fff
|
||||
style promtail fill:#bf360c,stroke:#8c2809,color:#fff
|
||||
style statsd fill:#bf360c,stroke:#8c2809,color:#fff
|
||||
style tempo fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style loki fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style prom fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style traces fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
style logs fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
style metrics fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
style corr fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **xrpld Node (three sources)**: A single node emits three independent data streams -- OpenTelemetry spans, PerfLog JSON logs, and Beast Insight StatsD metrics.
|
||||
- **Data Collection layer**: Each stream has its own collector -- OTel Collector for spans, Promtail/Fluentd for logs, and a StatsD exporter for metrics. They operate independently.
|
||||
- **Storage layer (Tempo, Loki, Prometheus)**: Each data type lands in a purpose-built store optimized for its query patterns (trace search, log grep, metric aggregation).
|
||||
- **Grafana Correlation Panel**: The key integration point -- Grafana queries all three stores and links them via shared fields (`trace_id`, `tx_hash`, `ledger_seq`), enabling a single-pane debugging experience.
|
||||
|
||||
### 7.7.2 Correlation Fields
|
||||
|
||||
| Source | Field | Link To | Purpose |
|
||||
| ----------- | ------------------- | ------------- | -------------------------- |
|
||||
| **Trace** | `trace_id` | Logs | Find log entries for trace |
|
||||
| **Trace** | `tx_hash` | Logs, Metrics | Find TX-related data |
|
||||
| **Trace** | `ledger_seq` | Logs | Find ledger-related logs |
|
||||
| **PerfLog** | `trace_id` (new) | Traces | Jump to trace from log |
|
||||
| **PerfLog** | `ledger_seq` | Traces | Find consensus trace |
|
||||
| **Insight** | `exemplar.trace_id` | Traces | Jump from metric spike |
|
||||
|
||||
### 7.7.3 Example: Debugging a Slow Transaction
|
||||
|
||||
**Step 1: Find the trace**
|
||||
|
||||
```
|
||||
# In Grafana Explore with Tempo
|
||||
{resource.service.name="xrpld" && span.tx_hash="ABC123..."}
|
||||
```
|
||||
|
||||
**Step 2: Get the trace_id from the trace view**
|
||||
|
||||
```
|
||||
Trace ID: 4bf92f3577b34da6a3ce929d0e0e4736
|
||||
```
|
||||
|
||||
**Step 3: Find related PerfLog entries**
|
||||
|
||||
```
|
||||
# In Grafana Explore with Loki
|
||||
{job="xrpld"} |= "4bf92f3577b34da6a3ce929d0e0e4736"
|
||||
```
|
||||
|
||||
**Step 4: Check Insight metrics for the time window**
|
||||
|
||||
```
|
||||
# In Grafana with Prometheus
|
||||
rate(xrpld_tx_applied_total[1m])
|
||||
@ timestamp_from_trace
|
||||
```
|
||||
|
||||
### 7.7.4 Unified Dashboard Example
|
||||
|
||||
A single dashboard (uid `xrpld-unified`) that ties traces, metrics, and logs together across the Tempo, Prometheus, and Loki datasources:
|
||||
|
||||
- **Transaction Latency (Traces)** (timeseries, Tempo): `histogram_over_time(duration)` of `tx.receive` spans.
|
||||
- **Transaction Rate (Metrics)** (timeseries, Prometheus): `rate(xrpld_tx_received_total[5m])` per instance, with a data link that opens the matching `tx.receive` traces in Tempo.
|
||||
- **Recent Logs** (logs, Loki): `{job="xrpld"} | json`.
|
||||
- **Trace Search** (table, Tempo): all `xrpld` traces, with per-row data links on `traceID` that jump to the trace in Tempo and to the correlated logs in Loki (`{job="xrpld"} |= "<traceID>"`).
|
||||
|
||||
The cross-datasource data links are what make this a single-pane debugging view; the correlation fields they rely on are listed in section 7.7.2.
|
||||
|
||||
---
|
||||
|
||||
_Previous: [Implementation Phases](./06-implementation-phases.md)_ | _Next: [Appendix](./08-appendix.md)_ | _Back to: [Overview](./OpenTelemetryPlan.md)_
|
||||
263
OpenTelemetryPlan/08-appendix.md
Normal file
263
OpenTelemetryPlan/08-appendix.md
Normal file
@@ -0,0 +1,263 @@
|
||||
# Appendix
|
||||
|
||||
> **Parent Document**: [OpenTelemetryPlan.md](./OpenTelemetryPlan.md)
|
||||
> **Related**: [Observability Backends](./07-observability-backends.md)
|
||||
|
||||
---
|
||||
|
||||
## 8.1 Glossary
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol | **TxQ** = Transaction Queue
|
||||
|
||||
| Term | Definition |
|
||||
| --------------------- | ---------------------------------------------------------- |
|
||||
| **Span** | A unit of work with start/end time, name, and attributes |
|
||||
| **Trace** | A collection of spans representing a complete request flow |
|
||||
| **Trace ID** | 128-bit unique identifier for a trace |
|
||||
| **Span ID** | 64-bit unique identifier for a span within a trace |
|
||||
| **Context** | Carrier for trace/span IDs across boundaries |
|
||||
| **Propagator** | Component that injects/extracts context |
|
||||
| **Sampler** | Decides which traces to record |
|
||||
| **Exporter** | Sends spans to backend |
|
||||
| **Collector** | Receives, processes, and forwards telemetry |
|
||||
| **OTLP** | OpenTelemetry Protocol (wire format) |
|
||||
| **W3C Trace Context** | Standard HTTP headers for trace propagation |
|
||||
| **Baggage** | Key-value pairs propagated across service boundaries |
|
||||
| **Resource** | Entity producing telemetry (service, host, etc.) |
|
||||
| **Instrumentation** | Code that creates telemetry data |
|
||||
|
||||
### xrpld-Specific Terms
|
||||
|
||||
| Term | Definition |
|
||||
| ----------------- | ------------------------------------------------------------- |
|
||||
| **Overlay** | P2P network layer managing peer connections |
|
||||
| **Consensus** | XRP Ledger consensus algorithm (RCL) |
|
||||
| **Proposal** | Validator's suggested transaction set for a ledger |
|
||||
| **Validation** | Validator's signature on a closed ledger |
|
||||
| **HashRouter** | Component for transaction deduplication |
|
||||
| **JobQueue** | Thread pool for asynchronous task execution |
|
||||
| **PerfLog** | Existing performance logging system in xrpld |
|
||||
| **Beast Insight** | Existing metrics framework in xrpld |
|
||||
| **PathFinding** | Payment path computation engine for cross-currency payments |
|
||||
| **TxQ** | Transaction queue managing fee-based prioritization |
|
||||
| **LoadManager** | Dynamic fee escalation based on network load |
|
||||
| **SHAMap** | SHA-256 hash-based map (Merkle trie variant) for ledger state |
|
||||
|
||||
### Phase 9–11 Terms
|
||||
|
||||
| Term | Definition |
|
||||
| --------------------------- | ------------------------------------------------------------------------- |
|
||||
| **MetricsRegistry** | Centralized class for OTel async gauge registrations (Phase 9) |
|
||||
| **ObservableGauge** | OTel Metrics SDK async instrument polled via callback at fixed intervals |
|
||||
| **PeriodicMetricReader** | OTel SDK component that invokes gauge callbacks at configurable intervals |
|
||||
| **CountedObject** | xrpld template that tracks live instance counts via atomic counters |
|
||||
| **TxQ** | Transaction queue managing fee escalation and ordering |
|
||||
| **Load Factor** | Combined multiplier affecting transaction cost (local, cluster, network) |
|
||||
| **OTel Collector Receiver** | Custom Go plugin that polls xrpld RPC and emits OTel metrics (Phase 11) |
|
||||
|
||||
---
|
||||
|
||||
## 8.2 Span Hierarchy Visualization
|
||||
|
||||
> **TxQ** = Transaction Queue
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph trace["Trace: Transaction Lifecycle"]
|
||||
rpc["rpc.request<br/>(entry point)"]
|
||||
validate["tx.validate"]
|
||||
relay["tx.relay<br/>(parent span)"]
|
||||
|
||||
subgraph peers["Peer Spans"]
|
||||
p1["peer.send<br/>Peer A"]
|
||||
p2["peer.send<br/>Peer B"]
|
||||
p3["peer.send<br/>Peer C"]
|
||||
end
|
||||
|
||||
subgraph pathfinding["PathFinding Spans"]
|
||||
pathfind["pathfind.request"]
|
||||
pathcomp["pathfind.compute"]
|
||||
end
|
||||
|
||||
consensus["consensus.round"]
|
||||
apply["tx.apply"]
|
||||
|
||||
subgraph txqueue["TxQ Spans"]
|
||||
txq["txq.enqueue"]
|
||||
txqApply["txq.apply"]
|
||||
end
|
||||
|
||||
feeCalc["fee.escalate"]
|
||||
end
|
||||
|
||||
subgraph validators["Validator Spans"]
|
||||
valFetch["validator.list.fetch"]
|
||||
valManifest["validator.manifest"]
|
||||
end
|
||||
|
||||
rpc --> validate
|
||||
rpc --> pathfind
|
||||
pathfind --> pathcomp
|
||||
validate --> relay
|
||||
relay --> p1
|
||||
relay --> p2
|
||||
relay --> p3
|
||||
p1 -.->|"context propagation"| consensus
|
||||
consensus --> apply
|
||||
apply --> txq
|
||||
txq --> txqApply
|
||||
txq --> feeCalc
|
||||
|
||||
style trace fill:#0f172a,stroke:#020617,color:#fff
|
||||
style peers fill:#1e3a8a,stroke:#172554,color:#fff
|
||||
style pathfinding fill:#134e4a,stroke:#0f766e,color:#fff
|
||||
style txqueue fill:#064e3b,stroke:#047857,color:#fff
|
||||
style validators fill:#4c1d95,stroke:#6d28d9,color:#fff
|
||||
style rpc fill:#1d4ed8,stroke:#1e40af,color:#fff
|
||||
style validate fill:#047857,stroke:#064e3b,color:#fff
|
||||
style relay fill:#047857,stroke:#064e3b,color:#fff
|
||||
style p1 fill:#0e7490,stroke:#155e75,color:#fff
|
||||
style p2 fill:#0e7490,stroke:#155e75,color:#fff
|
||||
style p3 fill:#0e7490,stroke:#155e75,color:#fff
|
||||
style consensus fill:#fef3c7,stroke:#fde68a,color:#1e293b
|
||||
style apply fill:#047857,stroke:#064e3b,color:#fff
|
||||
style pathfind fill:#0e7490,stroke:#155e75,color:#fff
|
||||
style pathcomp fill:#0e7490,stroke:#155e75,color:#fff
|
||||
style txq fill:#047857,stroke:#064e3b,color:#fff
|
||||
style txqApply fill:#047857,stroke:#064e3b,color:#fff
|
||||
style feeCalc fill:#047857,stroke:#064e3b,color:#fff
|
||||
style valFetch fill:#6d28d9,stroke:#4c1d95,color:#fff
|
||||
style valManifest fill:#6d28d9,stroke:#4c1d95,color:#fff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **rpc.request (blue, top)**: The entry point — every traced transaction starts as an RPC call; this root span is the parent of all downstream work.
|
||||
- **tx.validate and pathfind.request (green/teal, first fork)**: The RPC request fans out into transaction validation and, for cross-currency payments, a PathFinding branch (`pathfind.request` -> `pathfind.compute`).
|
||||
- **tx.relay -> Peer Spans (teal, middle)**: After validation, the transaction is relayed to peers A, B, and C in parallel; each `peer.send` is a sibling child span showing fan-out across the network.
|
||||
- **context propagation (dashed arrow)**: The dotted line from `peer.send Peer A` to `consensus.round` represents the trace context crossing a node boundary — the receiving validator picks up the same `trace_id` and continues the trace.
|
||||
- **consensus.round -> tx.apply -> TxQ Spans (green, lower)**: Once consensus accepts the transaction, it is applied to the ledger; the TxQ spans (`txq.enqueue`, `txq.apply`, `fee.escalate`) capture queue depth and fee escalation behavior.
|
||||
- **Validator Spans (purple, detached)**: `validator.list.fetch` and `validator.manifest` are independent workflows for UNL management — they run on their own traces and are linked to consensus via Span Links, not parent-child relationships.
|
||||
|
||||
---
|
||||
|
||||
## 8.3 References
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
### OpenTelemetry Resources
|
||||
|
||||
1. [OpenTelemetry C++ SDK](https://github.com/open-telemetry/opentelemetry-cpp)
|
||||
2. [OpenTelemetry Specification](https://opentelemetry.io/docs/specs/otel/)
|
||||
3. [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/)
|
||||
4. [OTLP Protocol Specification](https://opentelemetry.io/docs/specs/otlp/)
|
||||
|
||||
### Standards
|
||||
|
||||
5. [W3C Trace Context](https://www.w3.org/TR/trace-context/)
|
||||
6. [W3C Baggage](https://www.w3.org/TR/baggage/)
|
||||
7. [Protocol Buffers](https://protobuf.dev/)
|
||||
|
||||
### xrpld Resources
|
||||
|
||||
8. [xrpld Source Code](https://github.com/XRPLF/rippled)
|
||||
9. [XRP Ledger Documentation](https://xrpl.org/docs/)
|
||||
10. [xrpld Overlay README](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/README.md)
|
||||
11. [xrpld RPC README](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/README.md)
|
||||
12. [xrpld Consensus README](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/README.md)
|
||||
|
||||
---
|
||||
|
||||
## 8.4 Version History
|
||||
|
||||
| Version | Date | Author | Changes |
|
||||
| ------- | ---------- | ------ | -------------------------------------------------------------- |
|
||||
| 1.0 | 2026-02-12 | - | Initial implementation plan |
|
||||
| 1.1 | 2026-02-13 | - | Refactored into modular documents |
|
||||
| 1.2 | 2026-03-09 | - | Added Phases 9–11 (future enhancement plans) |
|
||||
| 1.3 | 2026-03-24 | - | Review fixes: accuracy corrections, cross-document consistency |
|
||||
|
||||
---
|
||||
|
||||
## 8.5 Document Index
|
||||
|
||||
### Plan Documents
|
||||
|
||||
| Document | Description |
|
||||
| -------------------------------------------------------------------- | -------------------------------------------------- |
|
||||
| [OpenTelemetryPlan.md](./OpenTelemetryPlan.md) | Master overview and executive summary |
|
||||
| [00-tracing-fundamentals.md](./00-tracing-fundamentals.md) | Distributed tracing concepts and OTel primer |
|
||||
| [01-architecture-analysis.md](./01-architecture-analysis.md) | xrpld architecture and trace points |
|
||||
| [02-design-decisions.md](./02-design-decisions.md) | SDK selection, exporters, span conventions |
|
||||
| [03-implementation-strategy.md](./03-implementation-strategy.md) | Directory structure, performance analysis |
|
||||
| [05-configuration-reference.md](./05-configuration-reference.md) | xrpld config, CMake, Collector configs |
|
||||
| [06-implementation-phases.md](./06-implementation-phases.md) | Timeline, tasks, risks, success metrics |
|
||||
| [07-observability-backends.md](./07-observability-backends.md) | Backend selection and architecture |
|
||||
| [08-appendix.md](./08-appendix.md) | Glossary, references, version history |
|
||||
| [secure-OTel.md](./secure-OTel.md) | Threat model and hardening (mTLS, peer validation) |
|
||||
| [09-data-collection-reference.md](./09-data-collection-reference.md) | Span/metric/dashboard inventory |
|
||||
|
||||
### Task Lists
|
||||
|
||||
| Document | Description |
|
||||
| -------------------------------------------------------------------------- | --------------------------------------------------- |
|
||||
| [Phase2_taskList.md](./Phase2_taskList.md) | RPC layer trace instrumentation |
|
||||
| [Phase3_taskList.md](./Phase3_taskList.md) | Peer overlay & consensus tracing |
|
||||
| [Phase4_taskList.md](./Phase4_taskList.md) | Transaction lifecycle tracing |
|
||||
| [Phase5_taskList.md](./Phase5_taskList.md) | Ledger processing & advanced tracing |
|
||||
| [Phase5_IntegrationTest_taskList.md](./Phase5_IntegrationTest_taskList.md) | Observability stack integration tests |
|
||||
| [Phase7_taskList.md](./Phase7_taskList.md) | Native OTel metrics migration |
|
||||
| [Phase8_taskList.md](./Phase8_taskList.md) | Log-trace correlation |
|
||||
| [Phase9_taskList.md](./Phase9_taskList.md) | Internal metric instrumentation gap fill (future) |
|
||||
| [Phase10_taskList.md](./Phase10_taskList.md) | Synthetic workload generation & validation (future) |
|
||||
| [Phase11_taskList.md](./Phase11_taskList.md) | Third-party data collection pipelines (future) |
|
||||
| [presentation.md](./presentation.md) | Presentation slides for OpenTelemetry plan overview |
|
||||
|
||||
> **Note**: Phases 1 and 6 do not have separate task list files. Phase 1 tasks are documented in [06-implementation-phases.md §6.2](./06-implementation-phases.md). Phase 6 tasks are documented in [06-implementation-phases.md §6.7](./06-implementation-phases.md).
|
||||
|
||||
---
|
||||
|
||||
## 8.6 Phase 9–11 Cross-Reference Guide
|
||||
|
||||
This guide maps Phase 9–11 content to its location across the documentation.
|
||||
|
||||
### Phase 9: Internal Metric Instrumentation Gap Fill
|
||||
|
||||
| Content | Location |
|
||||
| ------------------------------- | ------------------------------------------------------------------------ |
|
||||
| Plan & architecture | [06-implementation-phases.md §6.8.2](./06-implementation-phases.md) |
|
||||
| Task list (10 tasks) | [Phase9_taskList.md](./Phase9_taskList.md) |
|
||||
| Future metric definitions (~50) | [09-data-collection-reference.md §5b](./09-data-collection-reference.md) |
|
||||
| New class: `MetricsRegistry` | `src/xrpld/telemetry/MetricsRegistry.h/.cpp` (planned) |
|
||||
| New dashboards | `fee-market`, `job-queue` (planned) |
|
||||
|
||||
**Metric categories**: NodeStore I/O, Cache Hit Rates, TxQ, PerfLog Per-RPC, PerfLog Per-Job, Counted Objects, Fee Escalation & Load Factors.
|
||||
|
||||
### Phase 10: Synthetic Workload Generation & Telemetry Validation
|
||||
|
||||
| Content | Location |
|
||||
| -------------------- | ------------------------------------------------------------------------ |
|
||||
| Plan & architecture | [06-implementation-phases.md §6.8.3](./06-implementation-phases.md) |
|
||||
| Task list (7 tasks) | [Phase10_taskList.md](./Phase10_taskList.md) |
|
||||
| Validation inventory | [09-data-collection-reference.md §5c](./09-data-collection-reference.md) |
|
||||
| Test harness | `docker/telemetry/docker-compose.workload.yaml` (planned) |
|
||||
| CI workflow | `.github/workflows/telemetry-validation.yml` (planned) |
|
||||
|
||||
**Validates**: 16 spans, 22 attributes, 300+ metrics, 10 dashboards, log-trace correlation.
|
||||
|
||||
### Phase 11: Third-Party Data Collection Pipelines
|
||||
|
||||
| Content | Location |
|
||||
| --------------------------------- | ------------------------------------------------------------------------ |
|
||||
| Plan & architecture | [06-implementation-phases.md §6.8.4](./06-implementation-phases.md) |
|
||||
| Task list (11 tasks) | [Phase11_taskList.md](./Phase11_taskList.md) |
|
||||
| External metric definitions (~30) | [09-data-collection-reference.md §5d](./09-data-collection-reference.md) |
|
||||
| Custom OTel Collector receiver | `docker/telemetry/otel-rippled-receiver/` (planned) |
|
||||
| Prometheus alerting rules (11) | [09-data-collection-reference.md §5d](./09-data-collection-reference.md) |
|
||||
| New dashboards (4) | Validator Health, Network Topology, Fee Market (External), DEX & AMM |
|
||||
|
||||
**Consumer categories**: Exchanges, Payment Processors, DeFi/AMM, NFT Marketplaces, Analytics Providers, Wallets, Compliance, Academic Researchers, Institutional Custody, CBDC Bridge Operators.
|
||||
|
||||
---
|
||||
|
||||
_Previous: [Observability Backends](./07-observability-backends.md)_ | _Back to: [Overview](./OpenTelemetryPlan.md)_
|
||||
1321
OpenTelemetryPlan/09-data-collection-reference.md
Normal file
1321
OpenTelemetryPlan/09-data-collection-reference.md
Normal file
File diff suppressed because it is too large
Load Diff
226
OpenTelemetryPlan/OpenTelemetryPlan.md
Normal file
226
OpenTelemetryPlan/OpenTelemetryPlan.md
Normal file
@@ -0,0 +1,226 @@
|
||||
# [OpenTelemetry](00-tracing-fundamentals.md) Distributed Tracing Implementation Plan for xrpld (xrpld)
|
||||
|
||||
## Executive Summary
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
This document provides a comprehensive implementation plan for integrating OpenTelemetry distributed tracing into the xrpld XRP Ledger node software. The plan addresses the unique challenges of a decentralized peer-to-peer system where trace context must propagate across network boundaries between independent nodes.
|
||||
|
||||
### Key Benefits
|
||||
|
||||
- **End-to-end transaction visibility**: Track transactions from submission through consensus to ledger inclusion
|
||||
- **Consensus round analysis**: Understand timing and behavior of consensus phases across validators
|
||||
- **RPC performance insights**: Identify slow handlers and optimize response times
|
||||
- **Network topology understanding**: Visualize message propagation patterns between peers
|
||||
- **Incident debugging**: Correlate events across distributed nodes during issues
|
||||
|
||||
### Estimated Performance Overhead
|
||||
|
||||
| Metric | Overhead | Notes |
|
||||
| ------------- | ---------- | ------------------------------------------------ |
|
||||
| CPU | 1-3% | Span creation and attribute setting |
|
||||
| Memory | <10 MB | SDK statics + batch buffer + worker thread stack |
|
||||
| Network | 10-50 KB/s | Compressed OTLP export to collector |
|
||||
| Latency (p99) | <2% | With proper sampling configuration |
|
||||
|
||||
---
|
||||
|
||||
## Document Structure
|
||||
|
||||
This implementation plan is organized into modular documents for easier navigation:
|
||||
|
||||
<div align="center">
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
overview["📋 OpenTelemetryPlan.md<br/>(This Document)"]
|
||||
|
||||
subgraph fundamentals["Fundamentals"]
|
||||
fund["00-tracing-fundamentals.md"]
|
||||
end
|
||||
|
||||
subgraph analysis["Analysis & Design"]
|
||||
arch["01-architecture-analysis.md"]
|
||||
design["02-design-decisions.md"]
|
||||
end
|
||||
|
||||
subgraph impl["Implementation"]
|
||||
strategy["03-implementation-strategy.md"]
|
||||
config["05-configuration-reference.md"]
|
||||
end
|
||||
|
||||
subgraph deploy["Deployment & Planning"]
|
||||
phases["06-implementation-phases.md"]
|
||||
backends["07-observability-backends.md"]
|
||||
appendix["08-appendix.md"]
|
||||
secure["secure-OTel.md"]
|
||||
dataref["09-data-collection-reference.md"]
|
||||
end
|
||||
|
||||
overview --> fundamentals
|
||||
overview --> analysis
|
||||
overview --> impl
|
||||
overview --> deploy
|
||||
|
||||
fund --> arch
|
||||
arch --> design
|
||||
design --> strategy
|
||||
strategy --> config
|
||||
config --> phases
|
||||
phases --> backends
|
||||
backends --> appendix
|
||||
backends --> secure
|
||||
appendix --> dataref
|
||||
|
||||
style overview fill:#1b5e20,stroke:#0d3d14,color:#fff,stroke-width:2px
|
||||
style fundamentals fill:#00695c,stroke:#004d40,color:#fff
|
||||
style fund fill:#00695c,stroke:#004d40,color:#fff
|
||||
style analysis fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style impl fill:#bf360c,stroke:#8c2809,color:#fff
|
||||
style deploy fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
style arch fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style design fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style strategy fill:#bf360c,stroke:#8c2809,color:#fff
|
||||
style config fill:#bf360c,stroke:#8c2809,color:#fff
|
||||
style phases fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
style backends fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
style appendix fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
style secure fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
style dataref fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
| Section | Document | Description |
|
||||
| ------- | -------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
||||
| **0** | [Tracing Fundamentals](./00-tracing-fundamentals.md) | Distributed tracing concepts, span relationships, context propagation |
|
||||
| **1** | [Architecture Analysis](./01-architecture-analysis.md) | xrpld component analysis, trace points, instrumentation priorities |
|
||||
| **2** | [Design Decisions](./02-design-decisions.md) | SDK selection, exporters, span naming, attributes, context propagation |
|
||||
| **3** | [Implementation Strategy](./03-implementation-strategy.md) | Directory structure, key principles, performance optimization |
|
||||
| **5** | [Configuration Reference](./05-configuration-reference.md) | xrpld config, CMake integration, Collector configurations |
|
||||
| **6** | [Implementation Phases](./06-implementation-phases.md) | 5-phase timeline, tasks, risks, success metrics |
|
||||
| **7** | [Observability Backends](./07-observability-backends.md) | Backend selection guide and production architecture |
|
||||
| **8** | [Appendix](./08-appendix.md) | Glossary, references, version history |
|
||||
| **9** | [Data Collection Reference](./09-data-collection-reference.md) | Complete inventory of spans, attributes, metrics, and dashboards |
|
||||
| **Sec** | [Securing the OTel Pipeline](./secure-OTel.md) | Threat model and hardening (mTLS, peer trace-context validation) |
|
||||
|
||||
---
|
||||
|
||||
## 0. Tracing Fundamentals
|
||||
|
||||
This document introduces distributed tracing concepts for readers unfamiliar with the domain. It covers what traces and spans are, how parent-child and follows-from relationships model causality, how context propagates across service boundaries, and how sampling controls data volume. It also maps these concepts to xrpld-specific scenarios like transaction relay and consensus.
|
||||
|
||||
➡️ **[Read Tracing Fundamentals](./00-tracing-fundamentals.md)**
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture Analysis
|
||||
|
||||
> **WS** = WebSocket | **TxQ** = Transaction Queue
|
||||
|
||||
The xrpld node consists of several key components that require instrumentation for comprehensive distributed tracing. The main areas include the RPC server (HTTP/WebSocket), Overlay P2P network, Consensus mechanism (RCLConsensus), JobQueue for async task execution, PathFinding, Transaction Queue (TxQ), fee escalation (LoadManager), ledger acquisition, validator management, and existing observability infrastructure (PerfLog, Insight/StatsD, Journal logging).
|
||||
|
||||
Key trace points span across transaction submission via RPC, peer-to-peer message propagation, consensus round execution, ledger building, path computation, transaction queue behavior, fee escalation, and validator health. The implementation prioritizes high-value, low-risk components first: RPC handlers provide immediate value with minimal risk, while consensus tracing requires careful implementation to avoid timing impacts.
|
||||
|
||||
➡️ **[Read full Architecture Analysis](./01-architecture-analysis.md)**
|
||||
|
||||
---
|
||||
|
||||
## 2. Design Decisions
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol | **CNCF** = Cloud Native Computing Foundation
|
||||
|
||||
The OpenTelemetry C++ SDK is selected for its CNCF backing, active development, and native performance characteristics. Traces are exported via OTLP/gRPC (primary) or OTLP/HTTP (fallback) to an OpenTelemetry Collector, which provides flexible routing and sampling.
|
||||
|
||||
Span naming follows a hierarchical `<component>.<operation>` convention (e.g., `rpc.submit`, `tx.relay`, `consensus.round`). Context propagation uses W3C Trace Context headers for HTTP and embedded Protocol Buffer fields for P2P messages. The implementation coexists with existing PerfLog and Insight observability systems through correlation IDs.
|
||||
|
||||
**Data Collection & Privacy**: Telemetry collects only operational metadata (timing, counts, hashes) — never sensitive content (private keys, balances, amounts, raw payloads). Privacy protection includes account hashing, configurable redaction, sampling, and collector-level filtering. Node operators retain full control over telemetry configuration.
|
||||
|
||||
➡️ **[Read full Design Decisions](./02-design-decisions.md)**
|
||||
|
||||
---
|
||||
|
||||
## 3. Implementation Strategy
|
||||
|
||||
The telemetry code is organized under `include/xrpl/telemetry/` for headers and `src/libxrpl/telemetry/` for implementation. Key principles include RAII-based span management via `SpanGuard` (with `discard()` for dropping unwanted spans), a `FilteringSpanProcessor` that intercepts `OnEnd()` to prevent discarded spans from entering the export pipeline, conditional compilation with `XRPL_ENABLE_TELEMETRY`, and minimal runtime overhead through batch processing and efficient sampling.
|
||||
|
||||
Performance optimization strategies include head sampling fixed at 100% (intentionally not configurable, so trace keep/drop decisions stay coherent across nodes), tail-based sampling at the collector for errors and slow traces to reduce volume, batch export to reduce network overhead, and conditional instrumentation that compiles to no-ops when disabled.
|
||||
|
||||
➡️ **[Read full Implementation Strategy](./03-implementation-strategy.md)**
|
||||
|
||||
---
|
||||
|
||||
## 5. Configuration Reference
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol | **APM** = Application Performance Monitoring
|
||||
|
||||
Configuration is handled through the `[telemetry]` section in `xrpld.cfg` with options for enabling/disabling, exporter selection, endpoint configuration, sampling ratios, and component-level filtering. CMake integration includes a `XRPL_ENABLE_TELEMETRY` option for compile-time control.
|
||||
|
||||
OpenTelemetry Collector configurations are provided for development and production (with tail-based sampling, Tempo, and Elastic APM). Docker Compose examples enable quick local development environment setup.
|
||||
|
||||
➡️ **[View full Configuration Reference](./05-configuration-reference.md)**
|
||||
|
||||
---
|
||||
|
||||
## 6. Implementation Phases
|
||||
|
||||
The implementation spans 13 weeks across 8 phases:
|
||||
|
||||
| Phase | Duration | Focus | Key Deliverables |
|
||||
| ----- | ----------- | --------------------- | ----------------------------------------------------------- |
|
||||
| 1 | Weeks 1-2 | Core Infrastructure | SDK integration, Telemetry interface, Configuration |
|
||||
| 2 | Weeks 3-4 | RPC Tracing | HTTP context extraction, Handler instrumentation |
|
||||
| 3 | Weeks 5-6 | Transaction Tracing | Protocol Buffer context, Relay propagation |
|
||||
| 4 | Weeks 7-8 | Consensus Tracing | Round spans, Proposal/validation tracing |
|
||||
| 5 | Week 9 | Documentation | Runbook, Dashboards, Training |
|
||||
| 6 | Week 10 | StatsD Metrics Bridge | OTel Collector StatsD receiver, 3 Grafana dashboards |
|
||||
| 7 | Weeks 11-12 | Native OTel Metrics | OTelCollector impl, OTLP metrics export, StatsD deprecation |
|
||||
| 8 | Week 13 | Log-Trace Correlation | trace_id in logs, Loki ingestion, Tempo↔Loki linking |
|
||||
|
||||
**Total Effort**: 65.1 developer-days with 2 developers
|
||||
|
||||
➡️ **[View full Implementation Phases](./06-implementation-phases.md)**
|
||||
|
||||
---
|
||||
|
||||
## 7. Observability Backends
|
||||
|
||||
> **APM** = Application Performance Monitoring | **GCS** = Google Cloud Storage
|
||||
|
||||
Grafana Tempo is recommended for all environments due to its cost-effectiveness and Grafana integration, while Elastic APM is ideal for organizations with existing Elastic infrastructure.
|
||||
|
||||
The recommended production architecture uses a gateway collector pattern with regional collectors performing tail-based sampling, routing traces to multiple backends (Tempo for primary storage, Elastic for log correlation, S3/GCS for long-term archive).
|
||||
|
||||
➡️ **[View Observability Backend Recommendations](./07-observability-backends.md)**
|
||||
|
||||
---
|
||||
|
||||
## 8. Appendix
|
||||
|
||||
The appendix contains a glossary of OpenTelemetry and xrpld-specific terms, references to external documentation and specifications, version history for this implementation plan, and a complete document index.
|
||||
|
||||
➡️ **[View Appendix](./08-appendix.md)**
|
||||
|
||||
---
|
||||
|
||||
## 9. Data Collection Reference
|
||||
|
||||
A single-source-of-truth reference documenting every piece of telemetry data collected by xrpld. Covers all 16 OpenTelemetry spans with their 22 attributes, all StatsD metrics (gauges, counters, histograms, overlay traffic), SpanMetrics-derived Prometheus metrics, and all 10 Grafana dashboards. Includes Tempo search guides and Prometheus query examples.
|
||||
|
||||
➡️ **[View Data Collection Reference](./09-data-collection-reference.md)**
|
||||
|
||||
---
|
||||
|
||||
## Securing the OTel Pipeline
|
||||
|
||||
Threat model and hardening guidance for production deployments where xrpld nodes ship telemetry to a centrally-hosted collector across an untrusted network. Covers the two attack surfaces (collector ingress and peer trace-context spoofing) and the chosen defenses: mTLS as primary collector auth, NetworkPolicy as defense-in-depth, and source-side validation plus per-peer rate limiting for the `protocol::TraceContext` field on peer messages.
|
||||
|
||||
➡️ **[View Securing the OTel Pipeline](./secure-OTel.md)**
|
||||
|
||||
---
|
||||
|
||||
_This document provides a comprehensive implementation plan for integrating OpenTelemetry distributed tracing into the xrpld XRP Ledger node software. For detailed information on any section, follow the links to the corresponding sub-documents._
|
||||
258
OpenTelemetryPlan/Phase10_taskList.md
Normal file
258
OpenTelemetryPlan/Phase10_taskList.md
Normal file
@@ -0,0 +1,258 @@
|
||||
# Phase 10: Synthetic Workload Generation & Telemetry Validation — Task List
|
||||
|
||||
> **Status**: Future Enhancement
|
||||
>
|
||||
> **Goal**: Build tools that generate realistic XRPL traffic to validate the full Phases 1-9 telemetry stack end-to-end — all spans, attributes, metrics, dashboards, and log-trace correlation — under controlled load.
|
||||
>
|
||||
> **Scope**: Python/shell test harness + multi-node docker-compose environment + automated validation scripts + performance benchmarks.
|
||||
>
|
||||
> **Branch**: `pratik/otel-phase10-workload-validation` (from `pratik/otel-phase9-metric-gap-fill`)
|
||||
>
|
||||
> **Depends on**: Phase 9 (internal metric gap fill) — validates the full metric surface
|
||||
|
||||
### Related Plan Documents
|
||||
|
||||
| Document | Relevance |
|
||||
| -------------------------------------------------------------------- | --------------------------------------------------------------- |
|
||||
| [06-implementation-phases.md](./06-implementation-phases.md) | Phase 10 plan: motivation, architecture, exit criteria (§6.8.3) |
|
||||
| [09-data-collection-reference.md](./09-data-collection-reference.md) | Defines the full inventory of spans/metrics to validate |
|
||||
| [Phase9_taskList.md](./Phase9_taskList.md) | Prerequisite — all internal metrics must be emitting |
|
||||
|
||||
### Why This Phase Exists
|
||||
|
||||
Before Phases 1-9 can be considered production-ready, we need proof that:
|
||||
|
||||
1. All required spans fire with correct attributes under real transaction workloads
|
||||
2. All 255+ StatsD metrics + ~50 Phase 9 metrics appear in Prometheus with non-zero values
|
||||
3. Log-trace correlation (Phase 8) produces clickable trace_id links in Loki
|
||||
4. All 10 Grafana dashboards render meaningful data (no empty panels)
|
||||
5. Performance overhead stays within bounds (< 3% CPU, < 5MB memory)
|
||||
6. The telemetry stack survives sustained load without data loss or queue backpressure
|
||||
|
||||
---
|
||||
|
||||
## Task 10.1: Multi-Node Test Harness
|
||||
|
||||
**Objective**: Create a docker-compose environment with 3-5 validator nodes that produces real consensus rounds.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Create `docker/telemetry/docker-compose.workload.yaml`:
|
||||
- 5 xrpld validator nodes with UNL configured for each other
|
||||
- All telemetry enabled: `[telemetry] enabled=1`, `[insight] server=otel`
|
||||
- Full OTel stack: Collector, Tempo, Prometheus, Loki, Grafana
|
||||
- Shared network with service discovery
|
||||
|
||||
- Each node should:
|
||||
- Generate validator keys at startup
|
||||
- Configure all 5 nodes in its UNL
|
||||
- Enable all trace categories including `trace_peer=1`
|
||||
- Write logs to a file tailed by the OTel Collector filelog receiver
|
||||
|
||||
- Include a `Makefile` target: `make telemetry-workload-up` / `make telemetry-workload-down`
|
||||
|
||||
**Key files**:
|
||||
|
||||
- New: `docker/telemetry/docker-compose.workload.yaml`
|
||||
- New: `docker/telemetry/workload/generate-validator-keys.sh`
|
||||
- New: `docker/telemetry/workload/xrpld-validator.cfg.template`
|
||||
|
||||
---
|
||||
|
||||
## Task 10.2: RPC Load Generator
|
||||
|
||||
**Objective**: Configurable tool that fires all traced RPC commands at controlled rates.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Create `docker/telemetry/workload/rpc_load_generator.py`:
|
||||
- Connects to one or more xrpld WebSocket endpoints
|
||||
- Fires all RPC commands that have trace spans: `server_info`, `ledger`, `tx`, `account_info`, `account_lines`, `fee`, `submit`, etc.
|
||||
- Configurable parameters: rate (RPS), duration, command distribution weights
|
||||
- Injects `traceparent` HTTP headers to test W3C context propagation
|
||||
- Logs progress and errors to stdout
|
||||
|
||||
- Command distribution should match realistic production ratios:
|
||||
- 40% `server_info` / `fee` (health checks)
|
||||
- 30% `account_info` / `account_lines` / `account_objects` (wallet queries)
|
||||
- 15% `ledger` / `ledger_data` (explorer queries)
|
||||
- 10% `tx` / `account_tx` (transaction lookups)
|
||||
- 5% `book_offers` / `amm_info` (DEX queries)
|
||||
|
||||
**Key files**:
|
||||
|
||||
- New: `docker/telemetry/workload/rpc_load_generator.py`
|
||||
- New: `docker/telemetry/workload/requirements.txt`
|
||||
|
||||
---
|
||||
|
||||
## Task 10.3: Transaction Submitter
|
||||
|
||||
**Objective**: Generate diverse transaction types to exercise `tx.*` and `ledger.*` spans.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Create `docker/telemetry/workload/tx_submitter.py`:
|
||||
- Pre-funds test accounts from genesis account
|
||||
- Submits a mix of transaction types:
|
||||
- `Payment` (XRP and issued currencies) — exercises `tx.process`, `tx.apply`
|
||||
- `OfferCreate` / `OfferCancel` — DEX activity
|
||||
- `TrustSet` — trust line creation for issued currencies
|
||||
- `NFTokenMint` / `NFTokenCreateOffer` / `NFTokenAcceptOffer` — NFT activity
|
||||
- `EscrowCreate` / `EscrowFinish` — escrow lifecycle
|
||||
- `AMMCreate` / `AMMDeposit` / `AMMWithdraw` — AMM pool operations (if amendment enabled)
|
||||
- Configurable: TPS target, transaction mix weights, duration
|
||||
- Monitors submission results and tracks success/failure rates
|
||||
|
||||
- The transaction mix ensures the telemetry captures the full range of ledger activity that third parties care about.
|
||||
|
||||
**Key files**:
|
||||
|
||||
- New: `docker/telemetry/workload/tx_submitter.py`
|
||||
- New: `docker/telemetry/workload/test_accounts.json` (pre-generated keypairs)
|
||||
|
||||
---
|
||||
|
||||
## Task 10.4: Telemetry Validation Suite
|
||||
|
||||
**Objective**: Automated scripts that verify all expected telemetry data exists after a workload run.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Create `docker/telemetry/workload/validate_telemetry.py`:
|
||||
|
||||
**Span validation** (queries Tempo API):
|
||||
- Assert all required span names appear in traces (conditional spans — `grpc.*`,
|
||||
`ledger.acquire`, `txq.*`, `consensus.mode_change` — are marked `optional` and
|
||||
skipped when not exercised by the workload)
|
||||
- Assert each span has its required attributes (bare/underscore keys per the
|
||||
2026-05-13 span-attr naming redesign; dotted `xrpl.*` reserved for resource attrs)
|
||||
- Assert parent-child relationships are correct (`rpc.ws_message` → `rpc.process` → `rpc.command.*`)
|
||||
- Assert span durations are reasonable (> 0, < 60s)
|
||||
|
||||
**Metric validation** (queries Prometheus API):
|
||||
- Assert all SpanMetrics-derived metrics are non-zero: `traces_span_metrics_calls_total`, `traces_span_metrics_duration_milliseconds_bucket`
|
||||
- Assert all StatsD metrics are non-zero: `xrpld_LedgerMaster_Validated_Ledger_Age`, `xrpld_Peer_Finder_Active_*`, etc.
|
||||
- Assert all Phase 9 metrics are non-zero: `xrpld_nodestore_*`, `xrpld_cache_*`, `xrpld_txq_*`, `xrpld_rpc_method_*`, `xrpld_object_count`, `xrpld_load_factor*`
|
||||
- Assert metric label cardinality is within bounds
|
||||
|
||||
**Log-trace correlation validation** (queries Loki API):
|
||||
- Assert logs contain `trace_id=` and `span_id=` fields
|
||||
- Pick a random trace_id from Tempo → query Loki for matching logs → assert results exist
|
||||
- Assert Grafana derived field links are functional
|
||||
|
||||
**Dashboard validation**:
|
||||
- For each of the 10 Grafana dashboards, query the dashboard API and assert no panels show "No data"
|
||||
|
||||
- Output: JSON report with pass/fail per check, suitable for CI.
|
||||
|
||||
**Key files**:
|
||||
|
||||
- New: `docker/telemetry/workload/validate_telemetry.py`
|
||||
- New: `docker/telemetry/workload/expected_spans.json` (span inventory for validation)
|
||||
- New: `docker/telemetry/workload/expected_metrics.json` (metric inventory for validation)
|
||||
|
||||
---
|
||||
|
||||
## Task 10.5: Performance Benchmark Suite
|
||||
|
||||
**Objective**: Measure CPU/memory/latency overhead of the telemetry stack.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Create `docker/telemetry/workload/benchmark.sh`:
|
||||
- **Baseline run**: Start cluster with `[telemetry] enabled=0`, run transaction workload for 5 minutes, record metrics
|
||||
- **Telemetry run**: Start cluster with full telemetry enabled, run identical workload, record metrics
|
||||
- **Comparison**: Calculate deltas for:
|
||||
- CPU usage (per-node average)
|
||||
- Memory RSS (per-node peak)
|
||||
- RPC p99 latency
|
||||
- Transaction throughput (TPS)
|
||||
- Consensus round time p95
|
||||
- Ledger close time p95
|
||||
|
||||
- Output: Markdown table comparing baseline vs. telemetry, with pass/fail against targets:
|
||||
- CPU overhead < 3%
|
||||
- Memory overhead < 5MB
|
||||
- RPC latency impact < 2ms p99
|
||||
- Throughput impact < 5%
|
||||
- Consensus impact < 1%
|
||||
|
||||
- Store results in `docker/telemetry/workload/benchmark-results/` for historical tracking.
|
||||
|
||||
**Key files**:
|
||||
|
||||
- New: `docker/telemetry/workload/benchmark.sh`
|
||||
- New: `docker/telemetry/workload/collect_system_metrics.sh`
|
||||
|
||||
---
|
||||
|
||||
## Task 10.6: CI Integration
|
||||
|
||||
**Objective**: Wire the validation suite into CI for regression detection.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Create a CI workflow (GitHub Actions or equivalent) that:
|
||||
1. Builds xrpld with `-DXRPL_ENABLE_TELEMETRY=ON`
|
||||
2. Starts the multi-node workload harness
|
||||
3. Runs the RPC load generator + transaction submitter for 2 minutes
|
||||
4. Runs the validation suite
|
||||
5. Runs the benchmark suite
|
||||
6. Fails the build if any validation check fails or benchmark exceeds thresholds
|
||||
7. Archives the validation report and benchmark results as artifacts
|
||||
|
||||
- This should be a separate workflow (not part of the main CI), triggered manually or on telemetry-related branch changes.
|
||||
|
||||
**Key files**:
|
||||
|
||||
- New: `.github/workflows/telemetry-validation.yml`
|
||||
- New: `docker/telemetry/workload/run-full-validation.sh` (orchestrator script)
|
||||
|
||||
---
|
||||
|
||||
## Task 10.7: Documentation
|
||||
|
||||
**Objective**: Document the workload tools and validation process.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Create `docker/telemetry/workload/README.md`:
|
||||
- Quick start guide for running workload harness
|
||||
- Configuration options for load generator and tx submitter
|
||||
- How to read validation reports
|
||||
- How to run benchmarks and interpret results
|
||||
|
||||
- Update `docs/telemetry-runbook.md`:
|
||||
- Add "Validating Telemetry Stack" section
|
||||
- Add "Performance Benchmarking" section
|
||||
|
||||
- Update `OpenTelemetryPlan/09-data-collection-reference.md`:
|
||||
- Add "Validation" section with expected metric/span counts
|
||||
|
||||
---
|
||||
|
||||
## Exit Criteria — Delivered in PR #6519
|
||||
|
||||
- [x] Multi-node validator cluster starts and reaches consensus
|
||||
- [x] RPC load generator fires all traced RPC commands at configurable rates
|
||||
- [x] Transaction submitter generates 6+ transaction types at configurable TPS
|
||||
- [x] Validation suite confirms all required spans, attributes, and metrics
|
||||
- [x] Log-trace correlation validated end-to-end (Loki ↔ Tempo)
|
||||
- [x] Grafana dashboards render data (no empty panels)
|
||||
- [x] Overhead benchmark (`benchmark.sh`) measures telemetry-off vs telemetry-on deltas
|
||||
- [x] CI workflow runs validation on telemetry branch changes
|
||||
- [x] Validation report output is CI-parseable (JSON with exit codes)
|
||||
- [x] OTel-driven regression gate captures per-span/per-RPC/per-job timings from
|
||||
Prometheus and compares against a committed baseline
|
||||
|
||||
## Follow-up Work (tracked in separate PRs)
|
||||
|
||||
- [ ] FU-2: Automate baseline persistence across CI runs (artifact uploaded
|
||||
on merge to `develop`, downloaded on PR runs). Current mechanism
|
||||
requires a manual baseline-refresh PR.
|
||||
- [ ] FU-4: Replace the proxy measurements in `benchmark.sh` (wall-clock curl
|
||||
p99, ledger-cadence-as-TPS, ledger-cadence-as-consensus-p95) with
|
||||
PromQL quantile queries from the same pipeline the regression gate uses.
|
||||
- [ ] FU-6: Grafana dashboard plotting historical baseline values keyed by
|
||||
commit SHA, for triaging noisy regressions.
|
||||
544
OpenTelemetryPlan/Phase11_taskList.md
Normal file
544
OpenTelemetryPlan/Phase11_taskList.md
Normal file
@@ -0,0 +1,544 @@
|
||||
# Phase 11: Third-Party Data Collection Pipelines — Task List
|
||||
|
||||
> **Status**: Future Enhancement
|
||||
>
|
||||
> **Goal**: Build a custom OTel Collector receiver that periodically polls xrpld's admin RPCs and exports structured metrics for external consumers — making all XRPL health, validator, peer, fee, and DEX data available as Prometheus/OTLP metrics without xrpld code changes.
|
||||
>
|
||||
> **Scope**: Go-based OTel Collector receiver plugin + Grafana dashboards + Prometheus alerting rules.
|
||||
>
|
||||
> **Branch**: `pratik/otel-phase11-third-party-collection` (from `pratik/otel-phase10-workload-validation`)
|
||||
>
|
||||
> **Depends on**: Phase 10 (validation harness for testing the new receiver)
|
||||
|
||||
### Related Plan Documents
|
||||
|
||||
| Document | Relevance |
|
||||
| -------------------------------------------------------------------- | --------------------------------------------------------------- |
|
||||
| [06-implementation-phases.md](./06-implementation-phases.md) | Phase 11 plan: motivation, architecture, exit criteria (§6.8.4) |
|
||||
| [09-data-collection-reference.md](./09-data-collection-reference.md) | Defines full metric inventory including third-party metrics |
|
||||
| [Phase10_taskList.md](./Phase10_taskList.md) | Prerequisite — validation harness for testing |
|
||||
|
||||
### Third-Party Consumer Gap Analysis
|
||||
|
||||
This phase addresses the cross-cutting gap identified during research: **xrpld has no native Prometheus/OTLP metrics export for data accessible only via RPC**. Every consumer (exchanges, payment processors, analytics providers, validators, researchers, compliance firms, custodians) must build custom JSON-RPC polling and conversion. This receiver centralizes that work.
|
||||
|
||||
| Consumer Category | Data Unlocked by This Phase |
|
||||
| -------------------------- | ------------------------------------------------------------------ |
|
||||
| **Exchanges** | Real-time fee estimates, TxQ capacity, server health scores |
|
||||
| **Payment Processors** | Settlement latency percentiles, corridor health, path availability |
|
||||
| **Analytics Providers** | Validator metrics, network topology, amendment voting status |
|
||||
| **DeFi / AMM** | AMM pool TVL, DEX order book depth, trade volumes |
|
||||
| **Validators / Operators** | Per-peer latency, version distribution, UNL health, alerting |
|
||||
| **Compliance** | Transaction volume trends, network growth metrics |
|
||||
| **Academic Researchers** | Consensus performance time-series, decentralization metrics |
|
||||
| **CBDC / Tokenization** | Token supply tracking, trust line adoption, freeze status |
|
||||
| **Institutional Custody** | Multi-sig status, escrow tracking, reserve calculations |
|
||||
| **Wallet Providers** | Server health for node selection, fee prediction data |
|
||||
|
||||
---
|
||||
|
||||
## Task 11.1: OTel Collector Receiver Scaffold
|
||||
|
||||
**Objective**: Create the Go project structure for a custom OTel Collector receiver that polls xrpld JSON-RPC.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Create `docker/telemetry/otel-rippled-receiver/`:
|
||||
- `receiver.go` — implements `receiver.Metrics` interface
|
||||
- `config.go` — configuration struct (endpoint, poll interval, enabled RPCs)
|
||||
- `factory.go` — receiver factory registration
|
||||
- `go.mod` / `go.sum` — Go module with OTel Collector SDK dependency
|
||||
|
||||
- Configuration model:
|
||||
|
||||
```yaml
|
||||
xrpld_receiver:
|
||||
endpoint: "http://localhost:5005" # xrpld admin RPC
|
||||
poll_interval: 30s # how often to poll
|
||||
enabled_collectors:
|
||||
- server_info
|
||||
- get_counts
|
||||
- fee
|
||||
- peers
|
||||
- validators
|
||||
- feature
|
||||
- server_state
|
||||
amm_pools: [] # optional: AMM pool IDs to track
|
||||
book_offers_pairs: [] # optional: currency pairs for DEX depth
|
||||
```
|
||||
|
||||
- Build a custom OTel Collector binary that includes this receiver alongside the standard receivers.
|
||||
|
||||
**Key files**:
|
||||
|
||||
- New: `docker/telemetry/otel-rippled-receiver/receiver.go`
|
||||
- New: `docker/telemetry/otel-rippled-receiver/config.go`
|
||||
- New: `docker/telemetry/otel-rippled-receiver/factory.go`
|
||||
- New: `docker/telemetry/otel-rippled-receiver/go.mod`
|
||||
- New: `docker/telemetry/otel-rippled-receiver/Dockerfile`
|
||||
|
||||
---
|
||||
|
||||
## Task 11.2: server_info / server_state Collector
|
||||
|
||||
**Objective**: Poll `server_info` and `server_state` and export all fields as OTel metrics.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Implement `serverInfoCollector` that calls `server_info` (admin) and extracts:
|
||||
|
||||
**Node Health Gauges:**
|
||||
- `xrpl_server_state` (enum → int: disconnected=0, connected=1, syncing=2, tracking=3, full=4, proposing=5)
|
||||
- `xrpl_server_state_duration_seconds`
|
||||
- `xrpl_uptime_seconds`
|
||||
- `xrpl_io_latency_ms`
|
||||
- `xrpl_amendment_blocked` (0 or 1)
|
||||
- `xrpl_peers_count`
|
||||
- `xrpl_peer_disconnects_total`
|
||||
- `xrpl_peer_disconnects_resources_total`
|
||||
- `xrpl_jq_trans_overflow_total`
|
||||
|
||||
**Consensus Gauges:**
|
||||
- `xrpl_last_close_proposers`
|
||||
- `xrpl_last_close_converge_time_seconds`
|
||||
- `xrpl_validation_quorum`
|
||||
|
||||
**Ledger Gauges:**
|
||||
- `xrpl_validated_ledger_seq`
|
||||
- `xrpl_validated_ledger_age_seconds`
|
||||
- `xrpl_validated_ledger_base_fee_drops`
|
||||
- `xrpl_validated_ledger_reserve_base_drops`
|
||||
- `xrpl_validated_ledger_reserve_inc_drops`
|
||||
- `xrpl_close_time_offset_seconds` (0 when absent)
|
||||
|
||||
**Load Factor Gauges:**
|
||||
- `xrpl_load_factor`
|
||||
- `xrpl_load_factor_server`
|
||||
- `xrpl_load_factor_fee_escalation`
|
||||
- `xrpl_load_factor_fee_queue`
|
||||
- `xrpl_load_factor_local`
|
||||
- `xrpl_load_factor_net`
|
||||
- `xrpl_load_factor_cluster`
|
||||
|
||||
**State Accounting Gauges** (per state: disconnected, connected, syncing, tracking, full):
|
||||
- `xrpl_state_duration_seconds{state="<name>"}`
|
||||
- `xrpl_state_transitions_total{state="<name>"}`
|
||||
|
||||
**Validator Info** (when node is a validator):
|
||||
- `xrpl_validator_list_count`
|
||||
- `xrpl_validator_list_expiration_seconds` (epoch)
|
||||
- `xrpl_validator_list_active` (0 or 1)
|
||||
|
||||
**Key files**:
|
||||
|
||||
- New: `docker/telemetry/otel-rippled-receiver/collectors/server_info.go`
|
||||
|
||||
---
|
||||
|
||||
## Task 11.3: get_counts Collector
|
||||
|
||||
**Objective**: Poll `get_counts` and export internal object counts and NodeStore stats.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Implement `getCountsCollector`:
|
||||
|
||||
**Database Gauges:**
|
||||
- `xrpl_db_size_kb{db="total"}`, `xrpl_db_size_kb{db="ledger"}`, `xrpl_db_size_kb{db="transaction"}`
|
||||
|
||||
**NodeStore Gauges:**
|
||||
- `xrpl_nodestore_reads_total`, `xrpl_nodestore_reads_hit`, `xrpl_nodestore_writes_total`
|
||||
- `xrpl_nodestore_read_bytes`, `xrpl_nodestore_written_bytes`
|
||||
- `xrpl_nodestore_read_duration_us`, `xrpl_nodestore_write_load`
|
||||
- `xrpl_nodestore_read_queue`, `xrpl_nodestore_read_threads_running`
|
||||
|
||||
**Cache Gauges:**
|
||||
- `xrpl_cache_hit_rate{cache="SLE"}`, `xrpl_cache_hit_rate{cache="ledger"}`, `xrpl_cache_hit_rate{cache="accepted_ledger"}`
|
||||
- `xrpl_cache_size{cache="treenode"}`, `xrpl_cache_size{cache="fullbelow"}`, `xrpl_cache_size{cache="accepted_ledger"}`
|
||||
|
||||
**Object Count Gauges:**
|
||||
- `xrpl_object_count{type="<name>"}` for each counted object type (Transaction, Ledger, NodeObject, STTx, STLedgerEntry, InboundLedger, Pathfinder, etc.)
|
||||
|
||||
**Rates:**
|
||||
- `xrpl_historical_fetch_per_minute`
|
||||
- `xrpl_local_txs`
|
||||
|
||||
**Key files**:
|
||||
|
||||
- New: `docker/telemetry/otel-rippled-receiver/collectors/get_counts.go`
|
||||
|
||||
---
|
||||
|
||||
## Task 11.4: Peer Topology Collector
|
||||
|
||||
**Objective**: Poll `peers` and export per-peer and aggregate network metrics.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Implement `peersCollector`:
|
||||
|
||||
**Aggregate Gauges:**
|
||||
- `xrpl_peers_inbound_count`
|
||||
- `xrpl_peers_outbound_count`
|
||||
- `xrpl_peers_cluster_count`
|
||||
|
||||
**Per-Peer Gauges** (with labels `peer_key` truncated to 8 chars for cardinality control):
|
||||
- `xrpl_peer_latency_ms{peer="<key>", version="<ver>", inbound="<bool>"}`
|
||||
- `xrpl_peer_uptime_seconds{peer="<key>"}`
|
||||
- `xrpl_peer_load{peer="<key>"}`
|
||||
|
||||
**Distribution Gauges** (aggregated across all peers):
|
||||
- `xrpl_peer_latency_p50_ms`, `xrpl_peer_latency_p95_ms`, `xrpl_peer_latency_p99_ms`
|
||||
- `xrpl_peer_version_count{version="<semver>"}` — count of peers per software version
|
||||
|
||||
**Tracking Status:**
|
||||
- `xrpl_peer_diverged_count` — peers with `track=diverged`
|
||||
- `xrpl_peer_unknown_count` — peers with `track=unknown`
|
||||
|
||||
**Key files**:
|
||||
|
||||
- New: `docker/telemetry/otel-rippled-receiver/collectors/peers.go`
|
||||
|
||||
**Cardinality note**: Per-peer metrics use truncated keys. For large peer sets (50+), the aggregate distribution gauges are preferred over per-peer labels.
|
||||
|
||||
---
|
||||
|
||||
## Task 11.5: Validator & Amendment Collector
|
||||
|
||||
**Objective**: Poll `validators` and `feature` to export validator health and amendment voting status.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Implement `validatorCollector`:
|
||||
|
||||
**From `validators` RPC:**
|
||||
- `xrpl_trusted_validators_count`
|
||||
- `xrpl_validator_signing` (0 or 1 — whether local validator is signing)
|
||||
|
||||
**From `feature` RPC:**
|
||||
- `xrpl_amendment_enabled_count` — total enabled amendments
|
||||
- `xrpl_amendment_majority_count` — amendments with majority but not yet enabled
|
||||
- `xrpl_amendment_vetoed_count` — locally vetoed amendments
|
||||
- `xrpl_amendment_unsupported_majority` (0 or 1) — any unsupported amendment has majority (critical alert)
|
||||
|
||||
**Per-amendment with majority** (limited cardinality — only amendments with `majority` set):
|
||||
- `xrpl_amendment_majority_time{name="<amendment>"}` — epoch time when majority was gained
|
||||
- `xrpl_amendment_votes{name="<amendment>"}` — current vote count
|
||||
- `xrpl_amendment_threshold{name="<amendment>"}` — votes needed
|
||||
|
||||
**Key files**:
|
||||
|
||||
- New: `docker/telemetry/otel-rippled-receiver/collectors/validators.go`
|
||||
|
||||
---
|
||||
|
||||
## Task 11.6: Fee & TxQ Collector
|
||||
|
||||
**Objective**: Poll `fee` RPC and export real-time fee market data.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Implement `feeCollector` that calls the public `fee` RPC:
|
||||
|
||||
**Fee Level Gauges:**
|
||||
- `xrpl_fee_current_ledger_size` — transactions in current open ledger
|
||||
- `xrpl_fee_expected_ledger_size` — expected transactions at close
|
||||
- `xrpl_fee_max_queue_size` — maximum transaction queue size
|
||||
- `xrpl_fee_open_ledger_fee_drops` — minimum fee for open ledger inclusion
|
||||
- `xrpl_fee_median_fee_drops` — median fee level
|
||||
- `xrpl_fee_minimum_fee_drops` — base reference fee
|
||||
- `xrpl_fee_queue_size` — current queue depth
|
||||
|
||||
- This overlaps with Phase 9's internal TxQ metrics but provides an external-only collection path that doesn't require xrpld code changes.
|
||||
|
||||
**Key files**:
|
||||
|
||||
- New: `docker/telemetry/otel-rippled-receiver/collectors/fee.go`
|
||||
|
||||
---
|
||||
|
||||
## Task 11.7: DEX & AMM Collector (Optional)
|
||||
|
||||
**Objective**: Periodically poll configured AMM pools and order book pairs for DeFi metrics.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Implement `dexCollector` (enabled only when `amm_pools` or `book_offers_pairs` are configured):
|
||||
|
||||
**AMM Pool Gauges** (per configured pool):
|
||||
- `xrpl_amm_reserve{pool="<id>", asset="<currency>"}` — pool reserve amount
|
||||
- `xrpl_amm_lp_token_supply{pool="<id>"}` — outstanding LP tokens
|
||||
- `xrpl_amm_trading_fee{pool="<id>"}` — pool trading fee (basis points)
|
||||
- `xrpl_amm_tvl_drops{pool="<id>"}` — total value locked (XRP-denominated)
|
||||
|
||||
**Order Book Gauges** (per configured pair):
|
||||
- `xrpl_orderbook_bid_depth{pair="<base>/<quote>"}` — total bid volume
|
||||
- `xrpl_orderbook_ask_depth{pair="<base>/<quote>"}` — total ask volume
|
||||
- `xrpl_orderbook_spread{pair="<base>/<quote>"}` — best bid-ask spread
|
||||
- `xrpl_orderbook_offer_count{pair="<base>/<quote>", side="bid|ask"}` — number of offers
|
||||
|
||||
**Key files**:
|
||||
|
||||
- New: `docker/telemetry/otel-rippled-receiver/collectors/dex.go`
|
||||
|
||||
**Note**: This is optional because it requires explicit configuration of which pools/pairs to track. Default configuration tracks no DEX data.
|
||||
|
||||
---
|
||||
|
||||
## Task 11.8: Prometheus Alerting Rules
|
||||
|
||||
**Objective**: Create production-ready alerting rules for the metrics exported by this receiver.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Create `docker/telemetry/prometheus/rippled-alerts.yml`:
|
||||
|
||||
**Tier 1 — Critical (page immediately):**
|
||||
|
||||
```yaml
|
||||
- alert: XRPLServerNotFull
|
||||
expr: xrpl_server_state < 4
|
||||
for: 15m
|
||||
|
||||
- alert: XRPLAmendmentBlocked
|
||||
expr: xrpl_amendment_blocked == 1
|
||||
for: 1m
|
||||
|
||||
- alert: XRPLNoPeers
|
||||
expr: xrpl_peers_count == 0
|
||||
for: 5m
|
||||
|
||||
- alert: XRPLLedgerStale
|
||||
expr: xrpl_validated_ledger_age_seconds > 120
|
||||
for: 2m
|
||||
|
||||
- alert: XRPLHighIOLatency
|
||||
expr: xrpl_io_latency_ms > 100
|
||||
for: 5m
|
||||
|
||||
- alert: XRPLUnsupportedAmendmentMajority
|
||||
expr: xrpl_amendment_unsupported_majority == 1
|
||||
for: 1m
|
||||
```
|
||||
|
||||
**Tier 2 — Warning (investigate within hours):**
|
||||
|
||||
```yaml
|
||||
- alert: XRPLLowPeerCount
|
||||
expr: xrpl_peers_count < 10
|
||||
for: 15m
|
||||
|
||||
- alert: XRPLHighLoadFactor
|
||||
expr: xrpl_load_factor > 10
|
||||
for: 10m
|
||||
|
||||
- alert: XRPLSlowConsensus
|
||||
expr: xrpl_last_close_converge_time_seconds > 6
|
||||
for: 5m
|
||||
|
||||
- alert: XRPLValidatorListExpiring
|
||||
expr: (xrpl_validator_list_expiration_seconds - time()) < 86400
|
||||
for: 1h
|
||||
|
||||
- alert: XRPLClockDrift
|
||||
expr: xrpl_close_time_offset_seconds > 0
|
||||
for: 5m
|
||||
|
||||
- alert: XRPLStateFlapping
|
||||
expr: rate(xrpl_state_transitions_total{state="full"}[1h]) > 2
|
||||
for: 30m
|
||||
```
|
||||
|
||||
**Key files**:
|
||||
|
||||
- New: `docker/telemetry/prometheus/rippled-alerts.yml`
|
||||
- Update: `docker/telemetry/prometheus/prometheus.yml` (add rule_files reference)
|
||||
|
||||
---
|
||||
|
||||
## Task 11.9: New Grafana Dashboards
|
||||
|
||||
**Objective**: Create 4 new dashboards for the data exported by the receiver.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- **Validator Health** (`validator-health`):
|
||||
- Server state timeline, state duration breakdown
|
||||
- Proposer count trend, converge time trend, validation quorum
|
||||
- Validator list expiration countdown
|
||||
- Amendment voting status (majority/enabled/vetoed)
|
||||
|
||||
- **Network Topology** (`xrpld-network-topology`):
|
||||
- Peer count (inbound/outbound/cluster), peer version distribution
|
||||
- Peer latency distribution (p50/p95/p99), diverged peer count
|
||||
- Geographic distribution (if enriched with GeoIP)
|
||||
- Peer uptime distribution
|
||||
|
||||
- **Fee Market** (`xrpld-fee-market-external`):
|
||||
- Current fee levels (open ledger, median, minimum), fee escalation timeline
|
||||
- Queue depth vs. capacity, transactions per ledger
|
||||
- Load factor breakdown (server/network/cluster/escalation)
|
||||
|
||||
- **DEX & AMM Overview** (`xrpld-dex-amm`) (only populated when DEX collectors are configured):
|
||||
- AMM pool TVL, reserve ratios, LP token supply
|
||||
- Order book depth per pair, spread trends
|
||||
- Trading fee revenue estimates
|
||||
|
||||
**Key files**:
|
||||
|
||||
- New: `docker/telemetry/grafana/dashboards/rippled-validator-health.json`
|
||||
- New: `docker/telemetry/grafana/dashboards/rippled-network-topology.json`
|
||||
- New: `docker/telemetry/grafana/dashboards/rippled-fee-market-external.json`
|
||||
- New: `docker/telemetry/grafana/dashboards/rippled-dex-amm.json`
|
||||
|
||||
---
|
||||
|
||||
## Task 11.10: Integration with Phase 10 Validation
|
||||
|
||||
**Objective**: Extend the Phase 10 validation suite to verify this receiver's metrics.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Update `docker/telemetry/workload/validate_telemetry.py`:
|
||||
- Add assertions for all `xrpl_*` metrics produced by the receiver
|
||||
- Verify metric labels have expected values
|
||||
- Verify alerting rules fire correctly (inject a "bad" state and check alert)
|
||||
|
||||
- Update `docker/telemetry/docker-compose.workload.yaml`:
|
||||
- Add the custom OTel Collector build with the xrpld receiver
|
||||
- Configure the receiver to poll one of the test nodes
|
||||
|
||||
**Key files**:
|
||||
|
||||
- Update: `docker/telemetry/workload/validate_telemetry.py`
|
||||
- Update: `docker/telemetry/docker-compose.workload.yaml`
|
||||
- Update: `docker/telemetry/workload/expected_metrics.json`
|
||||
|
||||
---
|
||||
|
||||
## Task 11.11: Documentation
|
||||
|
||||
**Objective**: Document the receiver, its metrics, deployment, and alerting.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Create `docker/telemetry/otel-rippled-receiver/README.md`:
|
||||
- Architecture overview (how the receiver fits into the OTel Collector)
|
||||
- Configuration reference (all config options with defaults)
|
||||
- Metric reference table (all exported metrics with types and labels)
|
||||
- Deployment guide (building custom collector binary, docker-compose integration)
|
||||
|
||||
- Update `OpenTelemetryPlan/09-data-collection-reference.md`:
|
||||
- Add "Third-Party Metrics (OTel Collector Receiver)" section
|
||||
- Add new Grafana dashboard reference (4 dashboards)
|
||||
- Add alerting rules reference
|
||||
|
||||
- Update `docs/telemetry-runbook.md`:
|
||||
- Add "Third-Party Metrics Receiver" troubleshooting section
|
||||
- Add alerting playbook (what to do for each Tier 1/Tier 2 alert)
|
||||
|
||||
---
|
||||
|
||||
## Task 11.12: Alert Rules for External Dashboard Parity Metrics
|
||||
|
||||
> **Source**: [External Dashboard Parity](../docs/superpowers/specs/2026-03-30-external-dashboard-parity-design.md) — 18 alert rules ported from the community [xrpl-validator-dashboard](https://github.com/realgrapedrop/xrpl-validator-dashboard).
|
||||
>
|
||||
> **Upstream**: Phase 7 Tasks 7.9-7.16 (metrics), Phase 9 Tasks 9.11-9.13 (dashboards).
|
||||
> **Downstream**: None — terminal task in the parity chain.
|
||||
|
||||
**Objective**: Add Grafana alerting rules for the Phase 7+ parity metrics (validation agreement, validator health, peer quality, state tracking, ledger economy). These complement Task 11.8's `xrpl_*` alerts by covering the `xrpld_*` internal metrics.
|
||||
|
||||
**Critical Group** (8 rules, eval interval 10s):
|
||||
|
||||
| Rule | Condition | For |
|
||||
| ------------------- | ------------------------------------------------------------- | --- |
|
||||
| Agreement Below 90% | `xrpld_validation_agreement{metric="agreement_pct_24h"} < 90` | 30s |
|
||||
| Not Proposing | `xrpld_state_tracking{metric="state_value"} < 6` | 10s |
|
||||
| Unhealthy State | `xrpld_state_tracking{metric="state_value"} < 4` | 10s |
|
||||
| Amendment Blocked | `xrpld_validator_health{metric="amendment_blocked"} == 1` | 1m |
|
||||
| UNL Expiring | `xrpld_validator_health{metric="unl_expiry_days"} < 14` | 1h |
|
||||
| High IO Latency | `histogram_quantile(0.95, xrpld_ios_latency_bucket) > 50` | 1m |
|
||||
| High Load Factor | `xrpld_load_factor_metrics{metric="load_factor"} > 1000` | 1m |
|
||||
| Peer Count Critical | `xrpld_server_info{metric="peers"} < 5` | 1m |
|
||||
|
||||
**Network Group** (3 rules, eval interval 10s):
|
||||
|
||||
| Rule | Condition | For |
|
||||
| ------------------------- | ----------------------------------------------------------------- | --- |
|
||||
| Peer Drop >10% | `delta(xrpld_server_info{metric="peers"}[30s]) / ... * 100 < -10` | 30s |
|
||||
| Peer Drop >30% | Same formula, threshold -30 | 30s |
|
||||
| P90 Latency + Disconnects | `peer_latency_p90_ms > 500 AND rate(disconnects) > 0` | 2m |
|
||||
|
||||
**Performance Group** (7 rules, eval interval 10s):
|
||||
|
||||
| Rule | Condition | For |
|
||||
| ------------------- | ------------------------------------------------------------ | --- |
|
||||
| CPU High | Per-core CPU > 80% (requires node_exporter) | 2m |
|
||||
| Memory Critical | Memory usage > 90% (requires node_exporter) | 1m |
|
||||
| Disk Warning | Disk usage > 85% (requires node_exporter) | 2m |
|
||||
| Job Queue Overflow | `rate(xrpld_jq_trans_overflow_total[5m]) > 0` | 1m |
|
||||
| Upgrade Recommended | `xrpld_peer_quality{metric="peers_higher_version_pct"} > 60` | 1m |
|
||||
| TX Rate Drop | Transaction rate dropped > 50% in 5m window | 5m |
|
||||
| Stale Ledger | `xrpld_ledger_economy{metric="ledger_age_seconds"} > 30` | 1m |
|
||||
|
||||
**Notification channel templates**: Email/SMTP, Discord, Slack, PagerDuty.
|
||||
|
||||
**Key files**:
|
||||
|
||||
- New/extend: `docker/telemetry/grafana/alerting/alert-rules-parity.yaml`
|
||||
- New: `docker/telemetry/grafana/alerting/contact-points.yaml` (template configs)
|
||||
- New: `docker/telemetry/grafana/alerting/notification-policies.yaml`
|
||||
|
||||
**Exit Criteria**:
|
||||
|
||||
- [ ] All 18 rules evaluate without errors in Grafana alerting UI
|
||||
- [ ] Critical rules fire within expected timeframe when conditions are met
|
||||
- [ ] Notification channel templates are documented (not hard-coded to any service)
|
||||
|
||||
---
|
||||
|
||||
## Task 11.13: Dual-Datasource Architecture Documentation
|
||||
|
||||
> **Source**: [External Dashboard Parity](../docs/superpowers/specs/2026-03-30-external-dashboard-parity-design.md)
|
||||
|
||||
**Objective**: Document the external dashboard's "fast path" pattern as a future optimization for real-time panels.
|
||||
|
||||
**Pattern**: A lightweight Prometheus scrape endpoint (separate from OTLP pipeline) that polls critical metrics every 2-5s, bypassing the 10s OTLP metric reader interval and Prometheus scrape interval.
|
||||
|
||||
**Use case**: Real-time state panels (server state, ledger age, peer count) where 10-15s latency is too slow for operational dashboards.
|
||||
|
||||
**Decision**: Document as a future option, not implement now. The current 10s interval is acceptable for v1. The external dashboard achieves 2-5s freshness by polling RPC directly, which is what the Phase 11 receiver already does. Adding a separate scrape endpoint to xrpld would only be needed if sub-second metric freshness is required from the internal metrics pipeline.
|
||||
|
||||
**What to document**:
|
||||
|
||||
- Architecture comparison: OTLP pipeline (10-15s) vs. direct scrape (2-5s) vs. push gateway
|
||||
- When to consider: operator feedback indicating 10s is insufficient for alerting SLOs
|
||||
- How to implement if needed: add `/metrics` HTTP endpoint to xrpld with Prometheus client library
|
||||
- Trade-offs: additional port, additional dependency, duplication with OTLP metrics
|
||||
|
||||
**Key files**:
|
||||
|
||||
- Update: `OpenTelemetryPlan/09-data-collection-reference.md` (add "Future: Dual-Datasource Architecture" section)
|
||||
- Update: `docs/telemetry-runbook.md` (add brief note in performance tuning section)
|
||||
|
||||
**Exit Criteria**:
|
||||
|
||||
- [ ] Architecture comparison documented with clear trade-offs
|
||||
- [ ] Decision rationale recorded (why deferred, when to revisit)
|
||||
|
||||
---
|
||||
|
||||
## Exit Criteria
|
||||
|
||||
- [ ] Custom OTel Collector receiver builds and starts without errors
|
||||
- [ ] All `xrpl_*` metrics from server_info, get_counts, peers, validators, fee appear in Prometheus
|
||||
- [ ] Metrics update at configured poll interval (default 30s)
|
||||
- [ ] 4 new Grafana dashboards operational with data
|
||||
- [ ] Prometheus alerting rules fire correctly for simulated failure conditions
|
||||
- [ ] DEX/AMM collector works when configured (optional — not required for base exit criteria)
|
||||
- [ ] Phase 10 validation suite passes with receiver metrics included
|
||||
- [ ] Receiver handles xrpld restart/unavailability gracefully (no crash, logs warning, retries)
|
||||
- [ ] Documentation complete: receiver README, metric reference, alerting playbook
|
||||
- [ ] Go receiver has unit tests with >80% coverage
|
||||
- [ ] 18 Grafana alert rules for Phase 7+ parity metrics evaluate correctly (Task 11.12)
|
||||
- [ ] Dual-datasource architecture documented with trade-offs (Task 11.13)
|
||||
239
OpenTelemetryPlan/Phase2_taskList.md
Normal file
239
OpenTelemetryPlan/Phase2_taskList.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# Phase 2: RPC Tracing Completion Task List
|
||||
|
||||
> **Goal**: Complete RPC tracing coverage with unit tests, Grafana search filters, PathFind instrumentation, and config hardening. Build on the Phase 1c SpanGuard factory foundation to achieve production-quality RPC observability.
|
||||
>
|
||||
> **Scope**: Unit tests for core telemetry, Grafana Tempo search filters, PathFind RPC tracing, config validation (`std::clamp`).
|
||||
>
|
||||
> **Branch**: `pratik/otel-phase2-rpc-tracing` (from `pratik/otel-phase1c-rpc-integration`)
|
||||
|
||||
### Related Plan Documents
|
||||
|
||||
| Document | Relevance |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------- |
|
||||
| [04-code-samples.md](./04-code-samples.md) | TraceContextPropagator (§4.4.2), RPC instrumentation (§4.5.3) |
|
||||
| [02-design-decisions.md](./02-design-decisions.md) | W3C Trace Context (§2.5), span attributes (§2.4.2) |
|
||||
| [06-implementation-phases.md](./06-implementation-phases.md) | Phase 2 tasks (§6.3), definition of done (§6.11.2) |
|
||||
|
||||
---
|
||||
|
||||
## Task 2.1: W3C Trace Context HTTP Header Extraction
|
||||
|
||||
**Status**: DEFERRED → Phase 3
|
||||
|
||||
**Reason**: W3C context propagation (`traceparent`/`tracestate` headers) requires a consumer — in Phase 2, RPC spans are entirely local to the node. Phase 3 introduces cross-node transaction tracing via protobuf context propagation, which is the first use case for extracted trace context. Implementing it here without a consumer would be dead code.
|
||||
|
||||
**Implemented in**: `pratik/otel-phase3-tx-tracing` — `TraceContextPropagator.h/.cpp`
|
||||
|
||||
---
|
||||
|
||||
## Task 2.2: Per-Category Span Creation
|
||||
|
||||
**Status**: COMPLETE (superseded by Phase 1c design)
|
||||
|
||||
**Original plan**: Add `XRPL_TRACE_PEER` and `XRPL_TRACE_LEDGER` macros.
|
||||
|
||||
**Actual implementation**: Phase 1c replaced all tracing macros with the `SpanGuard::span(TraceCategory, prefix, name)` factory pattern. The `TraceCategory` enum (`Rpc`, `Transactions`, `Consensus`, `Peer`, `Ledger`) serves the same conditional-creation purpose without macros. No separate task needed — the factory already supports all categories.
|
||||
|
||||
---
|
||||
|
||||
## Task 2.3: Add shouldTraceLedger() to Telemetry Interface
|
||||
|
||||
**Objective**: The `Setup` struct has a `traceLedger` field but there's no corresponding virtual method. Add it for interface completeness.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Edit `include/xrpl/telemetry/Telemetry.h`:
|
||||
- Add `virtual bool shouldTraceLedger() const = 0;`
|
||||
|
||||
- Update all implementations:
|
||||
- `src/libxrpl/telemetry/Telemetry.cpp` (TelemetryImpl, NullTelemetryOtel)
|
||||
- `src/libxrpl/telemetry/NullTelemetry.cpp` (NullTelemetry)
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `include/xrpl/telemetry/Telemetry.h`
|
||||
- `src/libxrpl/telemetry/Telemetry.cpp`
|
||||
- `src/libxrpl/telemetry/NullTelemetry.cpp`
|
||||
|
||||
---
|
||||
|
||||
## Task 2.4: Unit Tests for Core Telemetry Infrastructure
|
||||
|
||||
**Status**: COMPLETE
|
||||
|
||||
**Objective**: Add unit tests for the core telemetry abstractions to validate correctness and catch regressions.
|
||||
|
||||
**Implemented**:
|
||||
|
||||
- `src/tests/libxrpl/telemetry/TelemetryConfig.cpp`:
|
||||
- Test Setup defaults (all fields have correct initial values)
|
||||
- Test `setupTelemetry` config parser (empty section, full section, edge cases)
|
||||
- Test `samplingRatio` clamping (values outside 0.0-1.0)
|
||||
|
||||
- `src/tests/libxrpl/telemetry/SpanGuardFactory.cpp`:
|
||||
- Test null guard methods are safe (setAttribute, setOk, setError, addEvent on null)
|
||||
- Test category span returns null when telemetry disabled
|
||||
- Test child/linked span null when no parent context
|
||||
- Test move construction transfers ownership
|
||||
- Test recordException safe on null guard
|
||||
- Test discard() safe on null guard
|
||||
|
||||
- `src/tests/libxrpl/telemetry/main.cpp` — GTest runner
|
||||
- `src/tests/libxrpl/CMakeLists.txt` — test target with optional OTel linking
|
||||
|
||||
---
|
||||
|
||||
## Task 2.5: Enhance RPC Span Attributes
|
||||
|
||||
**Status**: DEFERRED (low priority)
|
||||
|
||||
**Reason**: The high-value attributes (`command`, `version`, `role`, `status`) are already set by Phase 1c. The remaining HTTP transport-level attributes (`http.method`, `net.peer.ip`, `http.status_code`) provide limited additional insight since:
|
||||
|
||||
- `http.method` is always POST for JSON-RPC
|
||||
- `net.peer.ip` is debug-level info available in logs
|
||||
- `duration_ms` is redundant with span duration (OTel captures start/end time natively)
|
||||
|
||||
These can be added later if dashboard queries specifically need them. The node health attributes (Task 2.8) provide far more operational value and were prioritized instead.
|
||||
|
||||
---
|
||||
|
||||
## Task 2.6: Build Verification and Performance Baseline
|
||||
|
||||
**Objective**: Verify the build succeeds with and without telemetry, and establish a performance baseline.
|
||||
|
||||
**What to do**:
|
||||
|
||||
1. Build with `telemetry=ON` and verify no compilation errors
|
||||
2. Build with `telemetry=OFF` and verify no regressions
|
||||
3. Run existing unit tests to verify no breakage
|
||||
4. Document any build issues in lessons.md
|
||||
|
||||
**Verification Checklist**:
|
||||
|
||||
- [ ] `conan install . --build=missing -o telemetry=True` succeeds
|
||||
- [ ] `cmake --preset default -Dtelemetry=ON` configures correctly
|
||||
- [ ] Build succeeds with telemetry ON
|
||||
- [ ] Build succeeds with telemetry OFF
|
||||
- [ ] Existing tests pass with telemetry ON
|
||||
- [ ] Existing tests pass with telemetry OFF
|
||||
|
||||
---
|
||||
|
||||
## Task 2.8: RPC Span Attribute Enrichment — Node Health Context
|
||||
|
||||
**Status**: DROPPED.
|
||||
|
||||
Node health (`amendment_blocked`, `server_state`) is not part of the telemetry surface. Operators consume the same data via the existing `server_info` / `server_state` RPC commands, so duplicating it on traces adds storage and cardinality cost without new value. The OTel C++ SDK 1.18.0 also does not support runtime updates to the resource, ruling out resource-level emission of these dynamic-by-nature flags.
|
||||
|
||||
---
|
||||
|
||||
## Task 2.9: PathFind RPC Instrumentation
|
||||
|
||||
**Status**: COMPLETE
|
||||
|
||||
**Objective**: Trace the path_find and ripple_path_find RPC handlers to capture request latency and computation cost.
|
||||
|
||||
**Spans added**:
|
||||
|
||||
- `pathfind.request` — wraps `doPathFind()` and `doRipplePathFind()` RPC handlers
|
||||
- `pathfind.compute` — wraps `PathRequest::doUpdate()` (`pathfind_fast` attr)
|
||||
- `pathfind.update_all` — wraps `PathRequestManager::updateAll()` on ledger close (`pathfind_ledger_index`, `pathfind_num_requests` attrs; emitted only when active subscriptions exist)
|
||||
- `pathfind.discover` — wraps the entire per-source-asset loop in `PathRequest::findPaths()` (`pathfind_search_level`, `pathfind_num_paths` attrs). One span per RPC call instead of N (one per source asset). Trade-off: per-asset breakdown is lost; storage and cardinality bounded.
|
||||
|
||||
**Attribute namespacing**: All pathfind attributes use the `pathfind_*` underscore form per the Phase 1c naming-spec rule 5.
|
||||
|
||||
**New file**: `src/xrpld/rpc/detail/PathFindSpanNames.h`
|
||||
|
||||
**Modified files**:
|
||||
|
||||
- `src/xrpld/rpc/handlers/orderbook/PathFind.cpp`
|
||||
- `src/xrpld/rpc/handlers/orderbook/RipplePathFind.cpp`
|
||||
- `src/xrpld/rpc/detail/PathRequest.cpp`
|
||||
- `src/xrpld/rpc/detail/PathRequestManager.cpp`
|
||||
- `src/xrpld/rpc/detail/Pathfinder.cpp`
|
||||
|
||||
---
|
||||
|
||||
## Task 2.10: RPC and PathFind Span Attribute Gap Fill
|
||||
|
||||
**Status**: COMPLETE
|
||||
|
||||
**Objective**: Wire up workflow-identifying attributes that enable filtering and grouping traces by request characteristics without drilling into child spans.
|
||||
|
||||
**Attributes added**:
|
||||
|
||||
| Span | Attribute | Type | Source |
|
||||
| ------------------- | ---------------------------- | ------ | --------------------------------- |
|
||||
| `rpc.http_request` | `request_payload_size` | int64 | `request.body().size()` |
|
||||
| `rpc.process` | `is_batch` | bool | `method == "batch"` check |
|
||||
| `rpc.process` | `batch_size` | int64 | `params.size()` (only when batch) |
|
||||
| `rpc.ws_message` | `command` | string | `jv[command]` or `jv[method]` |
|
||||
| `rpc.command.*` | `load_type` | string | `context.loadType.label()` |
|
||||
| `pathfind.compute` | `pathfind_dest_amount` | string | `saDstAmount_.getFullText()` |
|
||||
| `pathfind.compute` | `pathfind_dest_currency` | string | `to_string(saDstAmount_.asset())` |
|
||||
| `pathfind.discover` | `pathfind_num_source_assets` | int64 | `sourceAssets.size()` |
|
||||
|
||||
**New attr keys**: `RpcSpanNames.h` (`isBatch`, `batchSize`, `loadType`), `PathFindSpanNames.h` (`destAmount`, `destCurrency`, `numSourceAssets`).
|
||||
|
||||
**Modified files**:
|
||||
|
||||
- `src/xrpld/rpc/detail/RpcSpanNames.h`
|
||||
- `src/xrpld/rpc/detail/PathFindSpanNames.h`
|
||||
- `src/xrpld/rpc/detail/ServerHandler.cpp`
|
||||
- `src/xrpld/rpc/detail/RPCHandler.cpp`
|
||||
- `src/xrpld/rpc/detail/PathRequest.cpp`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Task | Description | Status | Notes |
|
||||
| ---- | ------------------------------------------- | ------------------- | --------------------------------------------------------- |
|
||||
| 2.1 | W3C Trace Context header extraction | Deferred → Phase 3 | No consumer in Phase 2; needs cross-node tracing |
|
||||
| 2.2 | Per-category span creation | Complete (Phase 1c) | Superseded by TraceCategory enum + SpanGuard |
|
||||
| 2.3 | Add shouldTraceLedger() interface method | Complete (Phase 1c) | Delivered in Phase 1c base branch |
|
||||
| 2.4 | Unit tests for core telemetry | Complete | TelemetryConfig + SpanGuardFactory tests |
|
||||
| 2.5 | Enhanced RPC span attributes (HTTP-level) | Deferred | Low value; span duration covers timing natively |
|
||||
| 2.6 | Build verification and performance baseline | Complete | Verified in CI on Phase 1c |
|
||||
| 2.7 | Grafana Tempo search filters | Complete | rpc-command, rpc-status, rpc-role filters |
|
||||
| 2.8 | RPC span attribute enrichment (node health) | Dropped | Available via `server_info`/`server_state` RPC |
|
||||
| 2.9 | PathFind RPC instrumentation | Complete | request, compute, update_all, discover |
|
||||
| 2.10 | RPC/PathFind span attribute gap fill | Complete | Batch detection, payload size, load cost, pathfind params |
|
||||
|
||||
**Delivered in this branch**: Tasks 2.4, 2.7, 2.9, 2.10.
|
||||
**Deferred with rationale**: Tasks 2.1 (→Phase 3), 2.5 (low priority).
|
||||
**Dropped**: Task 2.8 (node health not duplicated on traces).
|
||||
**Superseded**: Task 2.2 (Phase 1c SpanGuard factory covers this).
|
||||
|
||||
---
|
||||
|
||||
## Known Issues / Future Work
|
||||
|
||||
### Thread safety of TelemetryImpl::stop() vs startSpan()
|
||||
|
||||
`TelemetryImpl::stop()` resets `sdkProvider_` (a `std::shared_ptr`) without
|
||||
synchronization. `getTracer()` reads the same member from RPC handler threads.
|
||||
This is a data race if any thread calls `startSpan()` concurrently with `stop()`.
|
||||
|
||||
**Current mitigation**: `Application::stop()` shuts down `serverHandler_`,
|
||||
`overlay_`, and `jobQueue_` before calling `telemetry_->stop()`, so no callers
|
||||
remain. See comments in `Telemetry.cpp:stop()` and `Application.cpp`.
|
||||
|
||||
**TODO**: Add an `std::atomic<bool> stopped_` flag checked in `getTracer()` to
|
||||
make this robust against future shutdown order changes.
|
||||
|
||||
### Macro incompatibility: XRPL_TRACE_SPAN vs XRPL_TRACE_SET_ATTR
|
||||
|
||||
`XRPL_TRACE_SPAN` and `XRPL_TRACE_SPAN_KIND` declare `_xrpl_guard_` as a bare
|
||||
`SpanGuard`, but `XRPL_TRACE_SET_ATTR` and `XRPL_TRACE_EXCEPTION` call
|
||||
`_xrpl_guard_.has_value()` which requires `std::optional<SpanGuard>`. Using
|
||||
`XRPL_TRACE_SPAN` followed by `XRPL_TRACE_SET_ATTR` in the same scope would
|
||||
fail to compile.
|
||||
|
||||
**Current mitigation**: No call site currently uses `XRPL_TRACE_SPAN` — all
|
||||
production code uses the conditional macros (`XRPL_TRACE_RPC`, `XRPL_TRACE_TX`,
|
||||
etc.) which correctly wrap the guard in `std::optional`.
|
||||
|
||||
**TODO**: Either make `XRPL_TRACE_SPAN`/`XRPL_TRACE_SPAN_KIND` also wrap in
|
||||
`std::optional`, or document that `XRPL_TRACE_SET_ATTR` is only compatible with
|
||||
the conditional macros.
|
||||
542
OpenTelemetryPlan/Phase3_taskList.md
Normal file
542
OpenTelemetryPlan/Phase3_taskList.md
Normal file
@@ -0,0 +1,542 @@
|
||||
# Phase 3: Transaction Tracing Task List
|
||||
|
||||
> **Goal**: Trace the full transaction lifecycle from RPC submission through peer relay, including cross-node context propagation via Protocol Buffer extensions. This is the WALK phase that demonstrates true distributed tracing.
|
||||
>
|
||||
> **Scope**: Protocol Buffer `TraceContext` message, context serialization, PeerImp transaction instrumentation, NetworkOPs processing instrumentation, HashRouter visibility, and multi-node relay context propagation.
|
||||
>
|
||||
> **Branch**: `pratik/otel-phase3-tx-tracing` (from `pratik/otel-phase2-rpc-tracing`)
|
||||
|
||||
### Related Plan Documents
|
||||
|
||||
| Document | Relevance |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ |
|
||||
| [04-code-samples.md](./04-code-samples.md) | TraceContext protobuf (§4.4.1), PeerImp instrumentation (§4.5.1), context serialization (§4.4.2) |
|
||||
| [01-architecture-analysis.md](./01-architecture-analysis.md) | Transaction flow (§1.3), key trace points (§1.6) |
|
||||
| [06-implementation-phases.md](./06-implementation-phases.md) | Phase 3 tasks (§6.4), definition of done (§6.11.3) |
|
||||
| [02-design-decisions.md](./02-design-decisions.md) | Context propagation design (§2.5), attribute schema (§2.4.3) |
|
||||
|
||||
---
|
||||
|
||||
## Task 3.1: Define TraceContext Protocol Buffer Message
|
||||
|
||||
**Objective**: Add trace context fields to the P2P protocol messages so trace IDs can propagate across nodes.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Edit `include/xrpl/proto/xrpl.proto` (or `src/xrpld/proto/ripple.proto`, wherever the proto is):
|
||||
- Add `TraceContext` message definition:
|
||||
```protobuf
|
||||
message TraceContext {
|
||||
bytes trace_id = 1; // 16-byte trace identifier
|
||||
bytes span_id = 2; // 8-byte span identifier
|
||||
uint32 trace_flags = 3; // bit 0 = sampled
|
||||
string trace_state = 4; // W3C tracestate value
|
||||
}
|
||||
```
|
||||
- Add `optional TraceContext trace_context = 1001;` to:
|
||||
- `TMTransaction`
|
||||
- `TMProposeSet` (for Phase 4 use)
|
||||
- `TMValidation` (for Phase 4 use)
|
||||
- Use high field numbers (1001+) to avoid conflicts with existing fields
|
||||
|
||||
- Regenerate protobuf C++ code
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `include/xrpl/proto/xrpl.proto` (or equivalent)
|
||||
|
||||
**Reference**:
|
||||
|
||||
- [04-code-samples.md §4.4.1](./04-code-samples.md) — TraceContext message definition
|
||||
- [02-design-decisions.md §2.5.2](./02-design-decisions.md) — Protocol buffer context propagation design
|
||||
|
||||
---
|
||||
|
||||
## Task 3.2: Implement Protobuf Context Serialization
|
||||
|
||||
**Objective**: Create utilities to serialize/deserialize OTel trace context to/from protobuf `TraceContext` messages.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Create `include/xrpl/telemetry/TraceContextPropagator.h` (extend from Phase 2 if exists, or add protobuf methods):
|
||||
- Add protobuf-specific methods:
|
||||
- `static Context extractFromProtobuf(protocol::TraceContext const& proto)` — reconstruct OTel context from protobuf fields
|
||||
- `static void injectToProtobuf(Context const& ctx, protocol::TraceContext& proto)` — serialize current span context into protobuf fields
|
||||
- Both methods guard behind `#ifdef XRPL_ENABLE_TELEMETRY`
|
||||
|
||||
- Create/extend `src/libxrpl/telemetry/TraceContextPropagator.cpp`:
|
||||
- Implement extraction: read trace_id (16 bytes), span_id (8 bytes), trace_flags from protobuf, construct `SpanContext`, wrap in `Context`
|
||||
- Implement injection: get current span from context, serialize its TraceId, SpanId, and TraceFlags into protobuf fields
|
||||
|
||||
**Key new/modified files**:
|
||||
|
||||
- `include/xrpl/telemetry/TraceContextPropagator.h`
|
||||
- `src/libxrpl/telemetry/TraceContextPropagator.cpp`
|
||||
|
||||
**Reference**:
|
||||
|
||||
- [04-code-samples.md §4.4.2](./04-code-samples.md) — Full extract/inject implementation
|
||||
|
||||
---
|
||||
|
||||
## Task 3.3: Instrument PeerImp Transaction Handling
|
||||
|
||||
**Objective**: Add trace spans to the peer-level transaction receive and relay path.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Edit `src/xrpld/overlay/detail/PeerImp.cpp`:
|
||||
- In `onMessage(TMTransaction)` / `handleTransaction()`:
|
||||
- Extract parent trace context from incoming `TMTransaction::trace_context` field (if present)
|
||||
- Create `tx.receive` span as child of extracted context (or new root if none)
|
||||
- Set attributes: `tx_hash`, `peer_id`, `tx_status`
|
||||
- On HashRouter suppression (duplicate): set `suppressed=true`, add `tx.duplicate` event
|
||||
- Wrap validation call with child span `tx.validate`
|
||||
- Wrap relay with `tx.relay` span
|
||||
- When relaying to peers:
|
||||
- Inject current trace context into outgoing `TMTransaction::trace_context`
|
||||
- Set `relay_count` attribute
|
||||
|
||||
- Use `SpanGuard::span(TraceCategory::Transactions, "tx", "receive")` factory
|
||||
(Phase 1c replaced macros with the SpanGuard factory pattern)
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/overlay/detail/PeerImp.cpp`
|
||||
|
||||
**Reference**:
|
||||
|
||||
- [04-code-samples.md §4.5.1](./04-code-samples.md) — Full PeerImp instrumentation example
|
||||
- [01-architecture-analysis.md §1.3](./01-architecture-analysis.md) — Transaction flow diagram
|
||||
- [01-architecture-analysis.md §1.6](./01-architecture-analysis.md) — tx.receive trace point
|
||||
|
||||
---
|
||||
|
||||
## Task 3.4: Instrument NetworkOPs Transaction Processing
|
||||
|
||||
**Objective**: Trace the transaction processing pipeline in NetworkOPs, covering both sync and async paths.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Edit `src/xrpld/app/misc/NetworkOPs.cpp`:
|
||||
- In `processTransaction()`:
|
||||
- Create `tx.process` span
|
||||
- Set attributes: `tx_hash`, `tx_type`, `local` (whether from RPC or peer)
|
||||
- Record whether sync or async path is taken
|
||||
|
||||
- In `doTransactionAsync()`:
|
||||
- Capture parent context before queuing
|
||||
- Create `tx.queue` span with queue depth attribute
|
||||
- Add event when transaction is dequeued for processing
|
||||
|
||||
- In `doTransactionSync()`:
|
||||
- Create `tx.process_sync` span
|
||||
- Record result (applied, queued, rejected)
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/app/misc/NetworkOPs.cpp`
|
||||
|
||||
**Reference**:
|
||||
|
||||
- [01-architecture-analysis.md §1.6](./01-architecture-analysis.md) — tx.validate and tx.process trace points
|
||||
- [02-design-decisions.md §2.4.3](./02-design-decisions.md) — Transaction attribute schema
|
||||
|
||||
---
|
||||
|
||||
## Task 3.5: Instrument HashRouter for Dedup Visibility
|
||||
|
||||
**Objective**: Make transaction deduplication visible in traces by recording HashRouter decisions as span attributes/events.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Edit `src/xrpld/overlay/detail/PeerImp.cpp` (in handleTransaction):
|
||||
- After calling `HashRouter::shouldProcess()` or `addSuppressionPeer()`:
|
||||
- Record `suppressed` attribute (true/false)
|
||||
- Record `tx_flags` showing current HashRouter state (SAVED, TRUSTED, etc.)
|
||||
- Add `tx.first_seen` or `tx.duplicate` event
|
||||
|
||||
- This is NOT a modification to HashRouter itself — just recording its decisions as span attributes in the existing PeerImp instrumentation from Task 3.3.
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/overlay/detail/PeerImp.cpp` (same changes as 3.3, logically grouped)
|
||||
|
||||
---
|
||||
|
||||
## Task 3.6: Context Propagation in Transaction Relay
|
||||
|
||||
**Status**: COMPLETE
|
||||
|
||||
**Objective**: Ensure trace context flows correctly when transactions are relayed between peers, creating linked spans across nodes.
|
||||
|
||||
**What was done**:
|
||||
|
||||
- **TX send side**: `NetworkOPs::apply()` now injects the tx.process span's trace
|
||||
context into the outgoing `TMTransaction` protobuf before relay, using
|
||||
`telemetry::injectSpanContext()`. The receiving node's `txReceiveSpan()` (already
|
||||
wired in PeerImp) extracts the parent span_id and creates the tx.receive span
|
||||
as a child of the sender's tx.process span.
|
||||
|
||||
- **Proposal send/receive**: `RCLConsensus::Adaptor::propose()` injects the
|
||||
current thread's active span context into the `TMProposeSet` protobuf via
|
||||
`telemetry::injectToProtobuf()`. PeerImp creates a
|
||||
`consensus.proposal.receive` span that extracts the sender's trace context
|
||||
as parent (via `ConsensusReceiveTracing.h`).
|
||||
|
||||
- **Validation send/receive**: `RCLConsensus::Adaptor::validate()` injects
|
||||
the current thread's active span context into the `TMValidation` protobuf.
|
||||
PeerImp creates a `consensus.validation.receive` span that extracts the
|
||||
sender's trace context as parent.
|
||||
|
||||
- **Edge cases**: Missing trace context (older peers) degrades gracefully to
|
||||
standalone spans. Invalid/corrupted context is treated as absent. Trace
|
||||
flags are propagated and respected.
|
||||
|
||||
**New infrastructure**:
|
||||
|
||||
- `SpanGuard::getTraceBytes()` — extracts raw trace_id/span_id/trace_flags
|
||||
from a span without exposing OTel types. Safe to call from any thread.
|
||||
- `PropagationHelpers.h` — `injectSpanContext(SpanGuard&, proto)` bridge
|
||||
between SpanGuard and protobuf TraceContext.
|
||||
- `TraceContextPropagator.h` — `injectToProtobuf(ctx, proto)` for
|
||||
same-thread injection via OTel RuntimeContext (used in propose/validate).
|
||||
- `ConsensusReceiveTracing.h` — `proposalReceiveSpan()` and
|
||||
`validationReceiveSpan()` helper functions that create receive spans with
|
||||
optional parent context extraction from incoming protobuf messages.
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/app/misc/NetworkOPs.cpp` — tx relay injection
|
||||
- `src/xrpld/app/consensus/RCLConsensus.cpp` — proposal/validation send injection
|
||||
- `src/xrpld/overlay/detail/PeerImp.cpp` — proposal/validation receive spans
|
||||
- `include/xrpl/telemetry/SpanGuard.h` — `TraceBytes` struct, `getTraceBytes()`
|
||||
- `src/libxrpl/telemetry/SpanGuard.cpp` — `getTraceBytes()` implementation
|
||||
- `src/xrpld/telemetry/PropagationHelpers.h` — inject helpers (new file)
|
||||
- `src/xrpld/telemetry/ConsensusReceiveTracing.h` — receive span helpers (new file)
|
||||
|
||||
**Reference**:
|
||||
|
||||
- [02-design-decisions.md §2.5](./02-design-decisions.md) — Context propagation design
|
||||
- [04-code-samples.md §4.5.1](./04-code-samples.md) — Relay context injection pattern
|
||||
|
||||
---
|
||||
|
||||
## Task 3.7: Build Verification and Testing
|
||||
|
||||
**Objective**: Verify all Phase 3 changes compile and work correctly.
|
||||
|
||||
**What to do**:
|
||||
|
||||
1. Build with `telemetry=ON` — verify no compilation errors
|
||||
2. Build with `telemetry=OFF` — verify no regressions
|
||||
3. Run existing unit tests
|
||||
4. Verify protobuf regeneration produces correct C++ code
|
||||
5. Document any issues encountered
|
||||
|
||||
**Verification Checklist**:
|
||||
|
||||
- [ ] Protobuf changes generate valid C++
|
||||
- [ ] Build succeeds with telemetry ON
|
||||
- [ ] Build succeeds with telemetry OFF
|
||||
- [ ] Existing tests pass
|
||||
- [ ] No undefined symbols from new telemetry calls
|
||||
|
||||
---
|
||||
|
||||
## Task 3.8: Transaction Span Peer Version Attribute
|
||||
|
||||
> **Source**: [External Dashboard Parity](../docs/superpowers/specs/2026-03-30-external-dashboard-parity-design.md) — adds peer version context inspired by the community [xrpl-validator-dashboard](https://github.com/realgrapedrop/xrpl-validator-dashboard).
|
||||
>
|
||||
> **Upstream**: Phase 2 (RPC span infrastructure must exist).
|
||||
> **Downstream**: Phase 10 (validation checks for this attribute).
|
||||
|
||||
**Objective**: Add the relaying peer's xrpld version to `tx.receive` spans so operators can correlate transaction issues with peer version mismatches during network upgrades.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Edit `src/xrpld/overlay/detail/PeerImp.cpp`:
|
||||
- In the `tx.receive` span block (after existing `peer_id` setAttribute call):
|
||||
- Add `peer_version` (string) — from `this->getVersion()`
|
||||
- Only set if `getVersion()` returns a non-empty string (avoid empty-string attributes)
|
||||
|
||||
**New span attribute**:
|
||||
|
||||
| Attribute | Type | Source | Example |
|
||||
| -------------- | ------ | -------------------- | --------------- |
|
||||
| `peer_version` | string | `peer->getVersion()` | `"xrpld-2.4.0"` |
|
||||
|
||||
**Rationale**: Transaction relay is where version mismatches cause subtle serialization or validation bugs. Tracing "this tx came from a v2.3.0 peer" helps diagnose compatibility issues. The community dashboard tracks peer versions externally; this brings version awareness into the trace itself.
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/overlay/detail/PeerImp.cpp`
|
||||
|
||||
**Exit Criteria**:
|
||||
|
||||
- [ ] `tx.receive` spans carry `peer_version` attribute with a non-empty version string
|
||||
- [ ] Attribute is omitted (not set to empty string) when `getVersion()` returns empty
|
||||
- [ ] Attribute visible in Tempo trace detail view
|
||||
|
||||
---
|
||||
|
||||
## Task 3.9: Deterministic Transaction Trace ID
|
||||
|
||||
> **Upstream**: Task 3.2 (protobuf serialization), Task 3.3 (PeerImp span exists).
|
||||
> **Downstream**: Phase 10 (workload validation can query by tx hash directly).
|
||||
> **Pattern**: Mirrors the consensus deterministic trace ID in Phase 4a
|
||||
> (`createDeterministicContext` in `RCLConsensus.cpp`), adapted for transactions.
|
||||
|
||||
**Objective**: Derive the trace_id for transaction spans deterministically from the
|
||||
transaction hash so that all nodes handling the same transaction independently produce
|
||||
spans under the same trace_id — regardless of whether protobuf context propagation
|
||||
succeeds.
|
||||
|
||||
**Why**: The current approach creates spans with random trace_ids and relies entirely
|
||||
on protobuf `TraceContext` propagation to link them. If any hop in the relay chain
|
||||
drops the context (older peers, message corruption, mixed-version networks), the trace
|
||||
splits and downstream spans become impossible to find. With deterministic trace_ids,
|
||||
correlation is guaranteed because every node derives the same trace_id from the same
|
||||
`txID`.
|
||||
|
||||
**Approach — deterministic trace_id + protobuf span_id propagation**:
|
||||
|
||||
1. Derive `trace_id = txHash[0:16]` (first 16 bytes of the 32-byte transaction hash).
|
||||
2. Generate a random 8-byte `span_id` per node (each node's span is unique within
|
||||
the shared trace).
|
||||
3. Create the span under this deterministic context as parent.
|
||||
4. **Additionally**, if protobuf `TraceContext` is present in the incoming
|
||||
`TMTransaction` message, extract the sender's `span_id` and use it as the span's
|
||||
parent — this preserves parent-child ordering in the trace tree.
|
||||
5. If protobuf context is absent (older peer, first hop), the span still has the
|
||||
correct deterministic `trace_id` — it appears as a sibling root in the same trace
|
||||
rather than being lost.
|
||||
|
||||
This gives the best of both worlds: guaranteed cross-node correlation via deterministic
|
||||
`trace_id`, plus parent-child relay ordering via protobuf `span_id` when available.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Create `createDeterministicTxContext(uint256 const& txHash)` utility function:
|
||||
- Location: shared header or file-local in `PeerImp.cpp` and `NetworkOPs.cpp`
|
||||
(or a shared telemetry utility if both need it).
|
||||
- Pattern: identical to `createDeterministicContext(uint256 const& ledgerId)` in
|
||||
`RCLConsensus.cpp` — take `txHash[0:16]` as trace_id, random span_id via
|
||||
`default_prng()`, sampled flag set, `remote=false`.
|
||||
- Guard behind `#ifdef XRPL_ENABLE_TELEMETRY`.
|
||||
|
||||
```cpp
|
||||
opentelemetry::context::Context
|
||||
createDeterministicTxContext(uint256 const& txHash)
|
||||
{
|
||||
namespace trace = opentelemetry::trace;
|
||||
|
||||
// First 16 bytes of the 32-byte tx hash as trace ID.
|
||||
trace::TraceId traceId(
|
||||
opentelemetry::nostd::span<uint8_t const, 16>(txHash.data(), 16));
|
||||
|
||||
// Random span_id so each node's span is unique within the trace.
|
||||
uint8_t spanIdBytes[8];
|
||||
auto const rval = default_prng()();
|
||||
std::memcpy(spanIdBytes, &rval, sizeof(spanIdBytes));
|
||||
trace::SpanId spanId(
|
||||
opentelemetry::nostd::span<uint8_t const, 8>(spanIdBytes, 8));
|
||||
|
||||
trace::SpanContext syntheticCtx(
|
||||
traceId, spanId, trace::TraceFlags(1), /* remote = */ false);
|
||||
|
||||
return opentelemetry::context::Context{}.SetValue(
|
||||
trace::kSpanKey,
|
||||
opentelemetry::nostd::shared_ptr<trace::Span>(
|
||||
new trace::DefaultSpan(syntheticCtx)));
|
||||
}
|
||||
```
|
||||
|
||||
- Edit `src/xrpld/overlay/detail/PeerImp.cpp` — restructure `handleTransaction()`:
|
||||
- **Move span creation after deserialization** (txID must be known first):
|
||||
1. Deserialize `STTx` and get `txID` (existing code at line ~1382).
|
||||
2. Create deterministic parent context: `auto detCtx = createDeterministicTxContext(txID)`.
|
||||
3. If `m->has_trace_context()`: extract protobuf context via `extractFromProtobuf()`,
|
||||
**combine** with deterministic trace_id — use the protobuf span_id as parent
|
||||
to preserve relay ordering, but override trace_id with the deterministic one.
|
||||
4. If no protobuf context: create span under `detCtx` directly.
|
||||
5. Set all existing attributes (`hash`, `peerId`, `peerVersion`, `suppressed`, etc.).
|
||||
|
||||
- **Combining deterministic trace_id with protobuf parent span_id**:
|
||||
When both are available, construct a synthetic `SpanContext` with:
|
||||
- `trace_id` = `txHash[0:16]` (deterministic)
|
||||
- `span_id` = extracted from protobuf (sender's span_id → becomes parent)
|
||||
- `trace_flags` = from protobuf
|
||||
- `remote` = true (came from another node)
|
||||
|
||||
```cpp
|
||||
// Pseudo-code for the combined context:
|
||||
auto detTraceId = trace::TraceId(txHash.data(), 16);
|
||||
auto remoteSpanId = /* from extractFromProtobuf */;
|
||||
auto remoteFlags = /* from extractFromProtobuf */;
|
||||
|
||||
trace::SpanContext combinedCtx(
|
||||
detTraceId, remoteSpanId, remoteFlags, /* remote = */ true);
|
||||
// Use as parent context for the new span.
|
||||
```
|
||||
|
||||
- Edit `src/xrpld/app/misc/NetworkOPs.cpp` — update `processTransaction()`:
|
||||
- `transaction->getID()` is already available at the top of the function.
|
||||
- Create deterministic parent context from `txID`.
|
||||
- Create `tx.process` span under this context.
|
||||
- No protobuf context to extract here (NetworkOPs is intra-node), so
|
||||
deterministic context alone is sufficient.
|
||||
|
||||
- Add `trace_strategy` attribute to spans:
|
||||
- Add `inline constexpr auto traceStrategy = "trace_strategy";`
|
||||
to `TxSpanNames.h`.
|
||||
- Set on each tx span: `span.setAttribute(tx_span::attr::traceStrategy, "deterministic")`.
|
||||
|
||||
**Key new/modified files**:
|
||||
|
||||
- `src/xrpld/overlay/detail/PeerImp.cpp` — restructured span creation
|
||||
- `src/xrpld/app/misc/NetworkOPs.cpp` — deterministic context for tx.process
|
||||
- `src/xrpld/app/misc/TxSpanNames.h` — new `traceStrategy` attribute constant
|
||||
- New or shared utility for `createDeterministicTxContext()` (location TBD: could be
|
||||
a shared header like `include/xrpl/telemetry/DeterministicContext.h`, or file-local
|
||||
if only used in two places)
|
||||
|
||||
**Interaction with existing tasks**:
|
||||
|
||||
- **Task 3.3 (PeerImp instrumentation)**: The span creation in `handleTransaction()`
|
||||
must be restructured — the span currently starts before `txID` is known. This task
|
||||
moves it after deserialization.
|
||||
- **Task 3.6 (Relay context propagation)**: Protobuf injection at the relay site
|
||||
remains the same — `injectToProtobuf()` serializes the current span's `span_id`.
|
||||
The receiver extracts it and combines with the deterministic `trace_id`.
|
||||
- **Phase 4a (Consensus deterministic trace ID)**: This task follows the same pattern.
|
||||
Consider extracting a shared utility (e.g., `createDeterministicContext(uint256)`)
|
||||
that both consensus and transaction tracing use.
|
||||
|
||||
**Exit Criteria**:
|
||||
|
||||
- [ ] `tx.receive` and `tx.process` spans have deterministic trace_id = `txHash[0:16]`
|
||||
- [ ] All nodes handling the same transaction produce spans under the same trace_id
|
||||
- [x] Protobuf `span_id` propagation still works when available (parent-child ordering)
|
||||
- [ ] Missing protobuf context (old peer) degrades gracefully to sibling spans, not lost traces
|
||||
- [ ] `trace_strategy` attribute set to `"deterministic"` on all tx spans
|
||||
- [ ] Trace queryable by tx hash (truncate hash → trace_id → direct lookup in Tempo)
|
||||
|
||||
**Deliverables implemented (not in original plan)**:
|
||||
|
||||
- **`SpanGuard::txSpan()` factory method** (`include/xrpl/telemetry/SpanGuard.h`):
|
||||
Two overloads for creating transaction spans with deterministic trace IDs:
|
||||
- `txSpan(category, group, name, txHash)` — standalone span (deterministic
|
||||
trace_id from `txHash[0:16]`, no parent span_id).
|
||||
- `txSpan(category, group, name, txHash, parentCtx)` — child span (deterministic
|
||||
trace_id combined with protobuf-extracted parent span_id for relay ordering).
|
||||
|
||||
- **`TxTracing.h` helper functions** (`src/xrpld/overlay/detail/TxTracing.h`):
|
||||
File-local helpers that wrap `SpanGuard::txSpan()` for the two main PeerImp call
|
||||
sites:
|
||||
- `txReceiveSpan(txHash, parentCtx)` — creates `tx.receive` span with
|
||||
deterministic trace_id and optional protobuf parent context.
|
||||
- `txProcessSpan(txHash)` — creates `tx.process` span with deterministic
|
||||
trace_id only (no protobuf parent, used intra-node).
|
||||
- **Note**: `TxTracing.h` includes `xrpl.pb.h` unconditionally (outside
|
||||
`#ifdef XRPL_ENABLE_TELEMETRY`) because `protocol::TMTransaction` appears in
|
||||
the function signatures regardless of telemetry build mode.
|
||||
|
||||
---
|
||||
|
||||
## Task 3.10: TxQ Instrumentation
|
||||
|
||||
**Status**: COMPLETE
|
||||
|
||||
**Objective**: Trace the transaction queue lifecycle — enqueue decisions, direct apply, batch clear, ledger-close accept loop, per-tx apply, and cleanup.
|
||||
|
||||
**Spans added**:
|
||||
|
||||
- `txq.enqueue` — wraps `TxQ::apply()` with tx_hash attribute
|
||||
- `txq.apply_direct` — wraps `TxQ::tryDirectApply()` fast-path
|
||||
- `txq.batch_clear` — wraps `TxQ::tryClearAccountQueueUpThruTx()`
|
||||
- `txq.accept` — wraps `TxQ::accept()` ledger-close dequeue with queue_size attr
|
||||
- `txq.accept_tx` — per-tx span inside accept loop with tx_hash, ter_code,
|
||||
retries_remaining attributes
|
||||
- `txq.cleanup` — wraps `TxQ::processClosedLedger()` with ledger_seq attribute
|
||||
|
||||
**New file**: `src/xrpld/app/misc/detail/TxQSpanNames.h`
|
||||
|
||||
**Modified file**: `src/xrpld/app/misc/detail/TxQ.cpp`
|
||||
|
||||
---
|
||||
|
||||
## Task 3.11: TX and TxQ Span Attribute Gap Fill
|
||||
|
||||
**Status**: COMPLETE
|
||||
|
||||
**Objective**: Add workflow-identifying attributes to transaction spans so operators can filter by transaction type and see outcomes without off-chain correlation.
|
||||
|
||||
**Attributes added**:
|
||||
|
||||
| Span | Attribute | Type | Source |
|
||||
| ----------------- | -------------------- | ------ | ------------------------------------------------------------------- |
|
||||
| `tx.process` | `tx_type` | string | `TxFormats::getInstance().findByType(stx->getTxnType())->getName()` |
|
||||
| `tx.process` | `fee` | int64 | `stx->getFieldAmount(sfFee).xrp().drops()` |
|
||||
| `tx.process` | `sequence` | int64 | `stx->getSeqProxy().value()` |
|
||||
| `tx.process` | `ter_result` | string | `transToken(e.result)` (set after batch application) |
|
||||
| `tx.process` | `applied` | bool | `e.applied` (set after batch application) |
|
||||
| `tx.receive` | `tx_type` | string | `TxFormats::getInstance().findByType(stx->getTxnType())->getName()` |
|
||||
| `txq.enqueue` | `tx_type` | string | same pattern as above |
|
||||
| `txq.enqueue` | `txq_status` | string | `queued` / `applied_direct` / `applied` / `rejected` |
|
||||
| `txq.enqueue` | `fee_level_paid` | int64 | `getFeeLevelPaid(view, *tx).value()` |
|
||||
| `txq.enqueue` | `required_fee_level` | int64 | `getRequiredFeeLevel(...).value()` |
|
||||
| `txq.batch_clear` | `num_cleared` | int64 | queued txs cleared ahead of the applying tx |
|
||||
| `txq.cleanup` | `expired_count` | int64 | entries dropped for passed `LastLedgerSequence` |
|
||||
| `txq.accept_tx` | `txq_status` | string | `applied` / `failed` / `retried` |
|
||||
| `txq.accept` | `ledger_changed` | bool | set at end of accept loop |
|
||||
|
||||
**New attr keys**: `TxSpanNames.h` (`txType`, `fee`, `sequence`, `terResult`, `applied`), `TxQSpanNames.h` (`txType`).
|
||||
|
||||
**Modified files**:
|
||||
|
||||
- `src/xrpld/app/misc/TxSpanNames.h`
|
||||
- `src/xrpld/app/misc/detail/TxQSpanNames.h`
|
||||
- `src/xrpld/app/misc/NetworkOPs.cpp`
|
||||
- `src/xrpld/overlay/detail/PeerImp.cpp`
|
||||
- `src/xrpld/app/misc/detail/TxQ.cpp`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Task | Description | New Files | Modified Files | Depends On |
|
||||
| ---- | ----------------------------------- | --------- | -------------- | ---------- |
|
||||
| 3.1 | TraceContext protobuf message | 0 | 1 | Phase 2 |
|
||||
| 3.2 | Protobuf context serialization | 1-2 | 0 | 3.1 |
|
||||
| 3.3 | PeerImp transaction instrumentation | 0 | 1 | 3.2 |
|
||||
| 3.4 | NetworkOPs transaction processing | 0 | 1 | Phase 2 |
|
||||
| 3.5 | HashRouter dedup visibility | 0 | 1 | 3.3 |
|
||||
| 3.6 | Relay context propagation | 0 | 1-2 | 3.3, 3.5 |
|
||||
| 3.7 | Build verification and testing | 0 | 0 | 3.1-3.6 |
|
||||
| 3.8 | TX span peer version attribute | 0 | 1 | 3.3 |
|
||||
| 3.9 | Deterministic transaction trace ID | 0-1 | 3 | 3.2, 3.3 |
|
||||
| 3.10 | TxQ instrumentation (6 spans) | 1 | 1 | 3.4 |
|
||||
| 3.11 | TX/TxQ span attribute gap fill | 0 | 5 | 3.3, 3.10 |
|
||||
|
||||
**Parallel work**: Tasks 3.1 and 3.4 can start in parallel. Task 3.2 depends on 3.1. Tasks 3.3 and 3.5 depend on 3.2. Task 3.6 depends on 3.3 and 3.5. Task 3.8 depends on 3.3 (span must exist). Task 3.9 depends on 3.2 and 3.3. Task 3.10 depends on 3.4 (tx.process span must exist).
|
||||
|
||||
**Exit Criteria** (from [06-implementation-phases.md §6.11.3](./06-implementation-phases.md)):
|
||||
|
||||
- [x] Transaction traces span across nodes
|
||||
- [x] Trace context in Protocol Buffer messages
|
||||
- [ ] HashRouter deduplication visible in traces
|
||||
- [ ] <5% overhead on transaction throughput
|
||||
- [x] Deterministic trace_id: same trace_id for same tx across all nodes
|
||||
- [x] Protobuf span_id propagation preserves parent-child ordering when available
|
||||
|
||||
---
|
||||
|
||||
## Known Issues / Future Work
|
||||
|
||||
### Unused trace_state proto field
|
||||
|
||||
The `TraceContext.trace_state` field (field 4) in `xrpl.proto` is reserved for
|
||||
W3C `tracestate` vendor-specific key-value pairs but is not read or written by
|
||||
`TraceContextPropagator`. Wire it when cross-vendor trace propagation is needed.
|
||||
No wire cost since proto `optional` fields are zero-cost when absent.
|
||||
940
OpenTelemetryPlan/Phase4_taskList.md
Normal file
940
OpenTelemetryPlan/Phase4_taskList.md
Normal file
@@ -0,0 +1,940 @@
|
||||
# Phase 4: Consensus Tracing Task List
|
||||
|
||||
> **Goal**: Full observability into consensus rounds — track round lifecycle, phase transitions, proposal handling, and validation. This is the RUN phase that completes the distributed tracing story.
|
||||
>
|
||||
> **Scope**: RCLConsensus instrumentation for round starts, phase transitions (open/establish/accept), proposal send/receive, validation handling, and correlation with transaction traces from Phase 3.
|
||||
>
|
||||
> **Branch**: `pratik/otel-phase4-consensus-tracing` (from `pratik/otel-phase3-tx-tracing`)
|
||||
|
||||
> **Note on attribute names**: the `xrpl.<domain>.<field>` keys shown below are
|
||||
> written in the older dotted form for readability — it mirrors how the fully
|
||||
> qualified attribute reads in a Tempo trace view. The implemented keys follow
|
||||
> the convention in [CONTRIBUTING.md](../CONTRIBUTING.md#telemetry-span-attribute-naming)
|
||||
> (underscore form, e.g. `consensus_round`, `consensus_mode`); the
|
||||
> `*SpanNames.h` constants are the single source of truth.
|
||||
|
||||
### Related Plan Documents
|
||||
|
||||
| Document | Relevance |
|
||||
| ------------------------------------------------------------ | ----------------------------------------------------------- |
|
||||
| [04-code-samples.md](./04-code-samples.md) | Consensus instrumentation (§4.5.2), consensus span patterns |
|
||||
| [01-architecture-analysis.md](./01-architecture-analysis.md) | Consensus round flow (§1.4), key trace points (§1.6) |
|
||||
| [06-implementation-phases.md](./06-implementation-phases.md) | Phase 4 tasks (§6.5), definition of done (§6.11.4) |
|
||||
| [02-design-decisions.md](./02-design-decisions.md) | Consensus attribute schema (§2.4.4) |
|
||||
|
||||
---
|
||||
|
||||
## Task 4.1: Instrument Consensus Round Start ✅
|
||||
|
||||
**Objective**: Create a root span for each consensus round that captures the round's key parameters.
|
||||
|
||||
**Status**: DONE (implemented via Task 4a.2 `startRoundTracing()` helper).
|
||||
|
||||
**What was done**:
|
||||
|
||||
- `RCLConsensus::Adaptor::startRoundTracing()` creates `consensus.round` span
|
||||
via `SpanGuard::hashSpan()` (deterministic) or `SpanGuard::span()` (attribute strategy)
|
||||
- Attributes set: `xrpl.consensus.ledger_id`, `xrpl.ledger.seq`,
|
||||
`xrpl.consensus.mode`, `trace_strategy`, `xrpl.consensus.round_id`
|
||||
- Round span stored as `roundSpan_` member in `RCLConsensus::Adaptor`
|
||||
- `roundSpanContext_` snapshot captured for cross-thread span linking
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/app/consensus/RCLConsensus.cpp`
|
||||
- `src/xrpld/app/consensus/RCLConsensus.h` (span and context members)
|
||||
|
||||
**Reference**:
|
||||
|
||||
- [04-code-samples.md §4.5.2](./04-code-samples.md) — startRound instrumentation example
|
||||
- [01-architecture-analysis.md §1.4](./01-architecture-analysis.md) — Consensus round flow
|
||||
|
||||
---
|
||||
|
||||
## Task 4.2: Instrument Phase Transitions ✅
|
||||
|
||||
**Objective**: Create child spans for each consensus phase (open, establish, accept) to show timing breakdown.
|
||||
|
||||
**Status**: DONE. All consensus phases are now instrumented:
|
||||
|
||||
- `consensus.establish` — created in `Consensus.h::startEstablishTracing()`
|
||||
- `consensus.ledger_close` — created in `RCLConsensus.cpp::onClose()`
|
||||
- `consensus.accept` / `consensus.accept.apply` — created in `onAccept()` / `doAccept()`
|
||||
- `consensus.phase.open` — `openSpan_` member in `Consensus.h`, created in `startRoundInternal()`, ended in `closeLedger()`
|
||||
|
||||
**Design notes**:
|
||||
|
||||
- `phase` attribute — phases are distinguished by span names instead
|
||||
- `phase.enter` / `phase.exit` events — not added (span start/end serves this purpose)
|
||||
- `phase_duration_ms` attribute — not set (span duration captures this)
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/app/consensus/RCLConsensus.cpp`
|
||||
- `src/xrpld/consensus/Consensus.h` (template-level establish phase tracking)
|
||||
|
||||
**Reference**:
|
||||
|
||||
- [04-code-samples.md §4.5.2](./04-code-samples.md) — phaseTransition instrumentation
|
||||
|
||||
---
|
||||
|
||||
## Task 4.3: Instrument Proposal Handling ✅
|
||||
|
||||
**Objective**: Trace proposal send and receive to show validator coordination.
|
||||
|
||||
**Status**: DONE. Both send and receive paths are instrumented.
|
||||
|
||||
**What was done**:
|
||||
|
||||
- In `Adaptor::propose()`:
|
||||
- Creates `consensus.proposal.send` span via `SpanGuard::span()`
|
||||
- Sets `xrpl.consensus.round` attribute
|
||||
|
||||
- In `PeerImp::onMessage(TMProposeSet)`:
|
||||
- Creates `consensus.proposal.receive` span
|
||||
- Sets `trusted` attribute (bool)
|
||||
|
||||
**Not implemented** (deferred to Phase 4b — cross-node propagation):
|
||||
|
||||
- `consensus.proposal.relay` span in `share(RCLCxPeerPos)` — requires trace context injection
|
||||
- Trace context injection/extraction for `TMProposeSet::trace_context`
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/app/consensus/RCLConsensus.cpp`
|
||||
|
||||
**Reference**:
|
||||
|
||||
- [04-code-samples.md §4.5.2](./04-code-samples.md) — peerProposal instrumentation
|
||||
- [02-design-decisions.md §2.4.4](./02-design-decisions.md) — Consensus attribute schema
|
||||
|
||||
---
|
||||
|
||||
## Task 4.4: Instrument Validation Handling ✅
|
||||
|
||||
**Objective**: Trace validation send and receive to show ledger validation flow.
|
||||
|
||||
**Status**: DONE. Both send and receive paths are instrumented.
|
||||
|
||||
**What was done**:
|
||||
|
||||
- In `Adaptor::validate()` (called from `doAccept()`):
|
||||
- Creates `consensus.validation.send` span via `Adaptor::createValidationSpan()`
|
||||
- Uses `SpanGuard::linkedSpan()` to create a follows-from link to the round span
|
||||
- Thread-safe: uses `roundSpanContext_` snapshot (captured on consensus thread,
|
||||
read on jtACCEPT thread)
|
||||
- Sets `xrpl.ledger.seq` and `proposing` attributes
|
||||
|
||||
- In `PeerImp::onMessage(TMValidation)`:
|
||||
- Creates `consensus.validation.receive` span
|
||||
- Sets `trusted` attribute (bool)
|
||||
- Sets `xrpl.ledger.seq` attribute
|
||||
|
||||
**Not implemented** (deferred to Phase 4b — cross-node propagation):
|
||||
|
||||
- Validated ledger hash, signing time attributes on send span (see Task 4.8)
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/app/consensus/RCLConsensus.cpp`
|
||||
|
||||
---
|
||||
|
||||
## Task 4.5: Add Consensus-Specific Attributes ✅
|
||||
|
||||
**Objective**: Enrich consensus spans with detailed attributes for debugging and analysis.
|
||||
|
||||
**Status**: DONE. All core attributes are set across various spans, including the previously missing `tx_count` and `disputes_count`.
|
||||
|
||||
**Implemented attributes** (across various spans):
|
||||
|
||||
- `xrpl.ledger.seq` — on `consensus.round`, `consensus.accept.apply`
|
||||
- `xrpl.consensus.round` — on `consensus.proposal.send`
|
||||
- `xrpl.consensus.mode` — on `consensus.round`, `consensus.ledger_close`
|
||||
- `proposers` — on `consensus.accept`, `consensus.establish`, `consensus.update_positions`
|
||||
- `converge_percent` — on `consensus.establish`, `consensus.update_positions`, `consensus.check`
|
||||
- `tx_count` — on `consensus.accept.apply` span (in `doAccept()`)
|
||||
- `disputes_count` — on `consensus.update_positions` span (in `updateOurPositions()`)
|
||||
|
||||
**Design notes**:
|
||||
|
||||
- `phase` — phases distinguished by span names instead
|
||||
- `phase_duration_ms` — span duration captures this
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/app/consensus/RCLConsensus.cpp`
|
||||
- `src/xrpld/consensus/Consensus.h`
|
||||
|
||||
---
|
||||
|
||||
## Task 4.6: Correlate Transaction and Consensus Traces ✅
|
||||
|
||||
**Objective**: Link transaction traces from Phase 3 with consensus traces so you can follow a transaction from submission through consensus into the ledger.
|
||||
|
||||
**Status**: DONE. Transaction-consensus correlation implemented via `tx.included` events in `doAccept()`.
|
||||
|
||||
**What was done**:
|
||||
|
||||
- In `doAccept()` (RCLConsensus.cpp):
|
||||
- Records `tx.included` events on the `consensus.accept.apply` span for each transaction in the accepted set
|
||||
- Each event includes `xrpl.tx.id` attribute with the transaction hash
|
||||
- This links consensus traces to individual transactions
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/app/consensus/RCLConsensus.cpp`
|
||||
|
||||
---
|
||||
|
||||
## Task 4.7: Build Verification and Testing ✅
|
||||
|
||||
**Objective**: Verify all Phase 4 changes compile and don't affect consensus timing.
|
||||
|
||||
**What to do**:
|
||||
|
||||
1. Build with `telemetry=ON` — verify no compilation errors
|
||||
2. Build with `telemetry=OFF` — verify no regressions (critical for consensus code)
|
||||
3. Run existing consensus-related unit tests
|
||||
4. Verify that `SpanGuard` factory methods compile to no-ops when disabled
|
||||
5. Check that no consensus-critical code paths are affected by instrumentation overhead
|
||||
|
||||
**Verification Checklist**:
|
||||
|
||||
- [x] Build succeeds with telemetry ON
|
||||
- [x] Build succeeds with telemetry OFF
|
||||
- [x] Existing consensus tests pass
|
||||
- [x] `SpanGuard` no-op implementation prevents overhead when telemetry is OFF
|
||||
- [x] Phase timing instrumentation doesn't use blocking operations
|
||||
|
||||
---
|
||||
|
||||
## Task 4.8: Consensus Validation Span Enrichment — NOT DONE
|
||||
|
||||
> **Source**: [External Dashboard Parity](../docs/superpowers/specs/2026-03-30-external-dashboard-parity-design.md) — adds validation agreement context inspired by the community [xrpl-validator-dashboard](https://github.com/realgrapedrop/xrpl-validator-dashboard).
|
||||
>
|
||||
> **Upstream**: Phase 4 tasks 4.1-4.4 (span creation must exist).
|
||||
> **Downstream**: Phase 7 (ValidationTracker reads these attributes), Phase 10 (validation checks).
|
||||
|
||||
**Objective**: Add ledger hash, validation type, and quorum data to consensus validation spans on both send and receive paths. This enables trace-level validation agreement analysis — filter by ledger hash to see which validators agreed for a given ledger.
|
||||
|
||||
**Status**: Not implemented. None of the enrichment attributes are set. The `consensus.validation.send` span only has `ledger.seq` and `proposing`. The `consensus.accept` span has `quorum` set to `result.proposers` (not the actual validator quorum from `app_.validators().quorum()`). No `PeerImp.cpp` changes were made.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Edit `src/xrpld/app/consensus/RCLConsensus.cpp`:
|
||||
- On the `consensus.validation.send` span (in `validate()` / `doAccept()`):
|
||||
- Add `xrpl.validation.ledger_hash` (string) — the ledger hash being validated
|
||||
- Add `xrpl.validation.full` (bool) — whether this is a full validation (not partial)
|
||||
- On the `consensus.accept` span (in `onAccept()`):
|
||||
- Add `validation_quorum` (int64) — from `app_.validators().quorum()`
|
||||
- Add `proposers_validated` (int64) — from `result.proposers`
|
||||
|
||||
- Edit `src/xrpld/overlay/detail/PeerImp.cpp`:
|
||||
- On the `peer.validation.receive` span:
|
||||
- Add `xrpl.peer.validation.ledger_hash` (string) — from deserialized `STValidation` object
|
||||
- Add `xrpl.peer.validation.full` (bool) — from `STValidation` flags
|
||||
|
||||
**New span attributes**:
|
||||
|
||||
| Span | Attribute | Type | Source |
|
||||
| --------------------------- | ---------------------------------- | ------ | --------------------------------- |
|
||||
| `consensus.validation.send` | `xrpl.validation.ledger_hash` | string | Ledger hash from validate() args |
|
||||
| `consensus.validation.send` | `xrpl.validation.full` | bool | Full vs partial validation |
|
||||
| `peer.validation.receive` | `xrpl.peer.validation.ledger_hash` | string | From STValidation deserialization |
|
||||
| `peer.validation.receive` | `xrpl.peer.validation.full` | bool | From STValidation flags |
|
||||
| `consensus.accept` | `validation_quorum` | int64 | `app_.validators().quorum()` |
|
||||
| `consensus.accept` | `proposers_validated` | int64 | `result.proposers` |
|
||||
|
||||
**Rationale**: The external dashboard's most valuable feature is validation agreement tracking. By recording the ledger hash on both outgoing and incoming validation spans, we create the raw data for agreement analysis at the trace level. Example Tempo query:
|
||||
|
||||
```
|
||||
{name="consensus.validation.send"} | xrpl.validation.ledger_hash = "A1B2C3..."
|
||||
```
|
||||
|
||||
Phase 7's `ValidationTracker` builds metric-level aggregation (1h/24h agreement %) on top of this data.
|
||||
|
||||
**Key modified files (not yet modified)**:
|
||||
|
||||
- `src/xrpld/app/consensus/RCLConsensus.cpp`
|
||||
- `src/xrpld/overlay/detail/PeerImp.cpp`
|
||||
|
||||
**Exit Criteria**:
|
||||
|
||||
- [x] `consensus.validation.send` spans carry `ledger_hash` and `full_validation`
|
||||
- [ ] `peer.validation.receive` spans carry `xrpl.peer.validation.ledger_hash` and `xrpl.peer.validation.full`
|
||||
- [ ] `consensus.accept` spans carry `validation_quorum` and `proposers_validated`
|
||||
- [x] Ledger hash attributes match between send and receive for the same ledger
|
||||
- [ ] No impact on consensus performance
|
||||
|
||||
---
|
||||
|
||||
## Task 4.9: Consensus Span Attribute Gap Fill
|
||||
|
||||
**Status**: COMPLETE
|
||||
|
||||
**Objective**: Add workflow-critical attributes to consensus spans that enable operators to understand consensus outcomes, identify bow-out proposals, and correlate validations to specific ledgers.
|
||||
|
||||
**Attributes added**:
|
||||
|
||||
| Span | Attribute | Type | Source |
|
||||
| --------------------------- | ----------------- | ------ | ------------------------------------- |
|
||||
| `consensus.proposal.send` | `is_bow_out` | bool | `proposal.isBowOut()` |
|
||||
| `consensus.accept` | `consensus_state` | string | `result.state` (yes/moved_on/expired) |
|
||||
| `consensus.accept` | `disputes_count` | int64 | `result.disputes.size()` |
|
||||
| `consensus.validation.send` | `ledger_hash` | string | `ledger.ledger->header().hash` |
|
||||
|
||||
**New attr keys**: `ConsensusSpanNames.h` (`isBowOut`, `ledgerHash`).
|
||||
|
||||
**Modified files**:
|
||||
|
||||
- `src/xrpld/consensus/ConsensusSpanNames.h`
|
||||
- `src/xrpld/app/consensus/RCLConsensus.cpp`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Task | Description | Status | New Files | Modified Files | Depends On |
|
||||
| ---- | ------------------------------------------- | ----------- | --------- | -------------- | ------------- |
|
||||
| 4.1 | Consensus round start instrumentation | ✅ Done | 0 | 2 | Phase 3 |
|
||||
| 4.2 | Phase transition instrumentation | ✅ Done | 0 | 1-2 | 4.1 |
|
||||
| 4.3 | Proposal handling instrumentation | ✅ Done | 0 | 2 | 4.1 |
|
||||
| 4.4 | Validation handling instrumentation | ✅ Done | 0 | 2 | 4.1 |
|
||||
| 4.5 | Consensus-specific attributes | ✅ Done | 0 | 2 | 4.2, 4.3, 4.4 |
|
||||
| 4.6 | Transaction-consensus correlation | ✅ Done | 0 | 1 | 4.2, Phase 3 |
|
||||
| 4.7 | Build verification and testing | ✅ Done | 0 | 0 | 4.1-4.6 |
|
||||
| 4.8 | Validation span enrichment (ext. dashboard) | ❌ Not done | 0 | 2 | 4.4 |
|
||||
| 4.9 | Consensus span attribute gap fill | ✅ Done | 0 | 2 | 4.1-4.5 |
|
||||
|
||||
**Parallel work**: Tasks 4.2, 4.3, and 4.4 can run in parallel after 4.1 is complete. Task 4.5 depends on all three. Task 4.6 depends on 4.2 and Phase 3. Task 4.8 depends on 4.4 (validation spans must exist).
|
||||
|
||||
### Implemented Spans
|
||||
|
||||
| Span Name | Method | Key Attributes |
|
||||
| --------------------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `consensus.proposal.send` | `Adaptor::propose` | `xrpl.consensus.round`, `is_bow_out` |
|
||||
| `consensus.ledger_close` | `Adaptor::onClose` | `xrpl.ledger.seq`, `xrpl.consensus.mode` |
|
||||
| `consensus.accept` | `Adaptor::onAccept` | `proposers`, `round_time_ms`, `quorum`, `disputes_count`, `consensus_state` |
|
||||
| `consensus.accept.apply` | `Adaptor::doAccept` | `close_time`, `close_time_correct`, `close_resolution_ms`, `consensus_state`, `proposing`, `round_time_ms`, `xrpl.ledger.seq`, `parent_close_time`, `close_time_self`, `close_time_vote_bins`, `resolution_direction` |
|
||||
| `consensus.validation.send` | `Adaptor::onAccept` (via validate) | `proposing`, `ledger_hash`, `ledger_seq`, `full_validation`, `validation_sign_time` |
|
||||
|
||||
#### Close Time Attributes (consensus.accept.apply)
|
||||
|
||||
The `consensus.accept.apply` span captures ledger close time agreement details
|
||||
driven by `avCT_CONSENSUS_PCT` (75% validator agreement threshold):
|
||||
|
||||
- **`close_time`** — Agreed-upon ledger close time (epoch seconds). When validators disagree (`consensusCloseTime == epoch`), this is synthetically set to `prevCloseTime + 1s`.
|
||||
- **`close_time_correct`** — `true` if validators reached agreement, `false` if they "agreed to disagree" (close time forced to prev+1s).
|
||||
- **`close_resolution_ms`** — Rounding granularity for close time (starts at 30s, decreases as ledger interval stabilizes).
|
||||
- **`consensus_state`** — `"finished"` (normal) or `"moved_on"` (consensus failed, adopted best available).
|
||||
- **`proposing`** — Whether this node was proposing.
|
||||
- **`round_time_ms`** — Total consensus round duration.
|
||||
- **`parent_close_time`** — Previous ledger's close time (epoch seconds). Enables computing close-time deltas across consecutive rounds without correlating separate spans.
|
||||
- **`close_time_self`** — This node's own proposed close time before consensus voting.
|
||||
- **`close_time_vote_bins`** — Number of distinct close-time vote bins from peer proposals. Higher values indicate less agreement among validators.
|
||||
- **`resolution_direction`** — Whether close-time resolution `"increased"` (coarser), `"decreased"` (finer), or stayed `"unchanged"` relative to the previous ledger.
|
||||
|
||||
**Exit Criteria** (from [06-implementation-phases.md §6.11.4](./06-implementation-phases.md)):
|
||||
|
||||
- [x] Complete consensus round traces
|
||||
- [x] Phase transitions visible (open, establish, close, accept)
|
||||
- [x] Proposals and validations traced — send and receive; relay deferred to Phase 4b
|
||||
- [x] Close time agreement tracked (per `avCT_CONSENSUS_PCT`)
|
||||
- [x] No impact on consensus timing
|
||||
- [x] Transaction-consensus correlation (Task 4.6) — `tx.included` events in doAccept
|
||||
- [ ] Validation span enrichment (Task 4.8) — not implemented
|
||||
|
||||
---
|
||||
|
||||
# Phase 4a: Establish-Phase Gap Fill & Cross-Node Correlation
|
||||
|
||||
> **Goal**: Fill tracing gaps in the consensus establish phase (disputes, convergence,
|
||||
> threshold escalation, mode changes) and establish cross-node correlation using a
|
||||
> deterministic shared trace ID derived from `previousLedger.id()`.
|
||||
>
|
||||
> **Approach**: Direct instrumentation in `Consensus.h` and `RCLConsensus.cpp`.
|
||||
> All spans use `SpanGuard` factory methods (`span()`, `hashSpan()`, `linkedSpan()`)
|
||||
> with `TraceCategory::Consensus` gating. Long-lived spans (round, establish) are
|
||||
> stored as `std::optional<SpanGuard>` class members. Short-lived scoped spans
|
||||
> (update_positions, check) are local variables. No macros are used — all tracing
|
||||
> is via direct `SpanGuard` API calls. `SpanGuard` compiles to no-ops when
|
||||
> telemetry is disabled.
|
||||
>
|
||||
> **Branch**: `pratik/otel-phase4-consensus-tracing`
|
||||
|
||||
## Design: Switchable Correlation Strategy
|
||||
|
||||
Two strategies for cross-node trace correlation, switchable via config:
|
||||
|
||||
### Strategy A — Deterministic Trace ID (Default)
|
||||
|
||||
Derive `trace_id = SHA256(previousLedger.id())[0:16]` so all nodes in the same
|
||||
consensus round share the same trace_id without P2P context propagation.
|
||||
|
||||
- **Pros**: All nodes appear in the same trace in Tempo automatically.
|
||||
No collector-side post-processing needed.
|
||||
- **Cons**: Overrides OTel's random trace_id generation; requires custom
|
||||
`IdGenerator` or manual span context construction.
|
||||
|
||||
### Strategy B — Attribute-Based Correlation
|
||||
|
||||
Use normal random trace_id but attach `xrpl.consensus.ledger_id` as an attribute
|
||||
on every consensus span. Correlation happens at query time via Tempo/Grafana
|
||||
`by attribute` queries.
|
||||
|
||||
- **Pros**: Standard OTel trace_id semantics; no SDK customization.
|
||||
- **Cons**: Cross-node correlation requires query-time joins, not automatic.
|
||||
|
||||
### Config
|
||||
|
||||
```ini
|
||||
[telemetry]
|
||||
# "deterministic" (default) or "attribute"
|
||||
consensus_trace_strategy=deterministic
|
||||
```
|
||||
|
||||
The C++ API to query this at runtime is `Telemetry::getConsensusTraceStrategy()`,
|
||||
which returns a `std::string const&` (`"deterministic"` or `"attribute"`).
|
||||
|
||||
### Implementation
|
||||
|
||||
In `RCLConsensus::Adaptor::startRound()`:
|
||||
|
||||
- If `deterministic`:
|
||||
1. Compute `trace_id_bytes = SHA256(prevLedgerID)[0:16]`
|
||||
2. Construct `opentelemetry::trace::TraceId(trace_id_bytes)`
|
||||
3. Create a synthetic `SpanContext` with this trace_id and a random span_id:
|
||||
```cpp
|
||||
auto traceId = opentelemetry::trace::TraceId(trace_id_bytes);
|
||||
auto spanId = opentelemetry::trace::SpanId(random_8_bytes);
|
||||
auto syntheticCtx = opentelemetry::trace::SpanContext(
|
||||
traceId, spanId, opentelemetry::trace::TraceFlags(1), false);
|
||||
```
|
||||
4. Wrap in `opentelemetry::context::Context` via
|
||||
`opentelemetry::trace::SetSpan(context, syntheticSpan)`
|
||||
5. Call `startSpan("consensus.round", parentContext)` so the new span
|
||||
inherits the deterministic trace_id.
|
||||
- If `attribute`: start a normal `consensus.round` span, set
|
||||
`xrpl.consensus.ledger_id = previousLedger.id()` as attribute.
|
||||
|
||||
Both strategies always set `xrpl.consensus.round_id` (round number) and
|
||||
`xrpl.consensus.ledger_id` (previous ledger hash) as attributes.
|
||||
|
||||
---
|
||||
|
||||
## Design: Span Hierarchy
|
||||
|
||||
```
|
||||
consensus.round (root — created in RCLConsensus::startRound, closed at accept)
|
||||
│ link → previous round's SpanContext (follows-from)
|
||||
│
|
||||
├── consensus.establish (phaseEstablish → acceptance, in Consensus.h)
|
||||
│ ├── consensus.update_positions (each updateOurPositions call)
|
||||
│ │ └── consensus.dispute.resolve (per-tx dispute resolution event)
|
||||
│ ├── consensus.check (each haveConsensus call)
|
||||
│ └── consensus.mode_change (short-lived span in adaptor on mode transition)
|
||||
│
|
||||
├── consensus.accept (existing onAccept span — reparented under round)
|
||||
│
|
||||
└── consensus.validation.send (existing — reparented, follows-from link to round)
|
||||
```
|
||||
|
||||
### Span Links (follows-from relationships)
|
||||
|
||||
| Link Source | Link Target | Rationale |
|
||||
| ----------------------------------------- | -------------------------- | ------------------------------------------------------------------------------ |
|
||||
| `consensus.round` (N+1) | `consensus.round` (N) | Causal chain: round N+1 exists because round N accepted |
|
||||
| `consensus.validation.send` | `consensus.round` | Validation follows from the round that produced it; may outlive the round span |
|
||||
| _(Phase 4b)_ Received proposal processing | Sender's `consensus.round` | Cross-node causal link via P2P context propagation |
|
||||
|
||||
---
|
||||
|
||||
## Task 4a.0: Prerequisites — Extend SpanGuard and Telemetry APIs ✅
|
||||
|
||||
**Objective**: Add missing API surface needed by later tasks.
|
||||
|
||||
**Status**: Done, but implemented differently than originally planned. The macro-based
|
||||
approach (`XRPL_TRACE_CONSENSUS`, `XRPL_TRACE_ADD_EVENT`, `XRPL_TRACE_SET_ATTR`) was
|
||||
**not used**. Instead, all consensus tracing uses `SpanGuard` factory methods and
|
||||
direct method calls, which is cleaner and avoids macro control-flow issues.
|
||||
|
||||
**What was done**:
|
||||
|
||||
1. **`SpanGuard::addEvent()` with attributes** — implemented as planned:
|
||||
|
||||
```cpp
|
||||
using EventAttribute = std::pair<std::string_view, std::string_view>;
|
||||
|
||||
void addEvent(std::string_view name,
|
||||
std::initializer_list<EventAttribute> attrs);
|
||||
```
|
||||
|
||||
Callers pass plain `string_view` pairs; the implementation converts internally.
|
||||
|
||||
```cpp
|
||||
// Actual usage in Consensus.h::updateOurPositions():
|
||||
span.addEvent(
|
||||
"dispute.resolve",
|
||||
{{consensus::span::attr::txId, to_string(txId)},
|
||||
{consensus::span::attr::disputeOurVote, dispute.getOurVote() ? "yes" : "no"}});
|
||||
```
|
||||
|
||||
2. **Span link support** — implemented via `SpanGuard::linkedSpan()` static factory
|
||||
instead of a `Telemetry::startSpan()` overload:
|
||||
|
||||
```cpp
|
||||
static SpanGuard linkedSpan(
|
||||
std::string_view name, SpanContext const& linkTarget);
|
||||
```
|
||||
|
||||
3. **No macros added** — `TracingInstrumentation.h` was not created. The `XRPL_TRACE_CONSENSUS`,
|
||||
`XRPL_TRACE_ADD_EVENT`, and `XRPL_TRACE_SET_ATTR` macros from the original plan were
|
||||
not implemented. All consensus tracing uses direct `SpanGuard` API:
|
||||
- `SpanGuard::span()` — create scoped spans
|
||||
- `SpanGuard::hashSpan()` — create spans with deterministic trace IDs
|
||||
- `SpanGuard::linkedSpan()` — create spans with follows-from links
|
||||
- `span.setAttribute()` — set attributes directly
|
||||
- `span.addEvent()` — add events directly
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `include/xrpl/telemetry/SpanGuard.h` — `addEvent()` overload, `EventAttribute` type alias
|
||||
- `src/libxrpl/telemetry/SpanGuard.cpp` — `addEvent()` implementation
|
||||
|
||||
---
|
||||
|
||||
## Task 4a.1: Adaptor `getTelemetry()` Method — NOT DONE (Not Needed)
|
||||
|
||||
**Objective**: Give `Consensus.h` access to the telemetry subsystem without
|
||||
coupling the generic template to OTel headers.
|
||||
|
||||
**Status**: Not implemented as specified. The `getTelemetry()` adaptor method was
|
||||
not needed because `SpanGuard::span()` is a static factory method that internally
|
||||
checks telemetry state via the global `Telemetry` singleton. `Consensus.h` creates
|
||||
spans by calling `SpanGuard::span(TraceCategory::Consensus, ...)` directly, without
|
||||
needing adaptor access. Only `RCLConsensus::Adaptor` uses `app_.getTelemetry()`
|
||||
directly (for `getConsensusTraceStrategy()` in `startRoundTracing()`).
|
||||
|
||||
**Key insight**: The `XRPL_TRACE_*` macro approach would have required
|
||||
`adaptor_.getTelemetry()`. Since macros were not used, this task became unnecessary.
|
||||
|
||||
---
|
||||
|
||||
## Task 4a.2: Switchable Round Span with Deterministic Trace ID ✅
|
||||
|
||||
**Objective**: Create a `consensus.round` root span in `startRound()` that uses
|
||||
the switchable correlation strategy. Store span context as a member for child
|
||||
spans in `Consensus.h`.
|
||||
|
||||
**Status**: Done. Implemented in `Adaptor::startRoundTracing()`.
|
||||
|
||||
**What was done**:
|
||||
|
||||
- `RCLConsensus::Adaptor::startRoundTracing()` helper:
|
||||
- Reads `consensus_trace_strategy` via `app_.getTelemetry().getConsensusTraceStrategy()`
|
||||
- **Deterministic**: uses `SpanGuard::hashSpan()` with `prevLgr.id()` data
|
||||
- **Attribute**: uses `SpanGuard::span(TraceCategory::Consensus, seg::consensus, "round")`
|
||||
- Sets attributes: `xrpl.consensus.ledger_id`, `xrpl.ledger.seq`, `xrpl.consensus.mode`, `trace_strategy`, `xrpl.consensus.round_id`
|
||||
- Captures `roundSpanContext_` snapshot for cross-thread span linking
|
||||
- Saves `prevRoundContext_` from previous round for follows-from links
|
||||
|
||||
- **`SpanGuard::hashSpan()` factory**: encapsulates deterministic trace ID logic:
|
||||
|
||||
```cpp
|
||||
static SpanGuard hashSpan(
|
||||
TraceCategory cat, std::string_view name,
|
||||
std::uint8_t const* hashData, std::size_t hashSize);
|
||||
```
|
||||
|
||||
Derives `trace_id = hashData[0:16]` so all nodes in the same round share
|
||||
the same trace_id. Compiles to no-op when telemetry is disabled.
|
||||
|
||||
- `consensus_trace_strategy` config parsed in `TelemetryConfig.cpp`,
|
||||
stored in `Telemetry::Setup`, accessible via `Telemetry::getConsensusTraceStrategy()`
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/app/consensus/RCLConsensus.cpp` — `startRoundTracing()` implementation
|
||||
- `src/xrpld/app/consensus/ConsensusSpanNames.h` — **(new)** compile-time span name and attribute key constants
|
||||
- `include/xrpl/telemetry/Telemetry.h` — `consensusTraceStrategy` in Setup, `getConsensusTraceStrategy()`
|
||||
- `src/libxrpl/telemetry/TelemetryConfig.cpp` — parse new config option
|
||||
|
||||
---
|
||||
|
||||
## Task 4a.3: Span Members in `Consensus.h` ✅
|
||||
|
||||
**Objective**: Add span storage to the `Consensus` class so that spans created
|
||||
in `startRound()` (adaptor) are accessible from `phaseEstablish()`,
|
||||
`updateOurPositions()`, and `haveConsensus()` (template methods).
|
||||
|
||||
**Status**: Done with documented plan deviation.
|
||||
|
||||
**What was done**:
|
||||
|
||||
- `establishSpan_` added to `Consensus` private members (as planned):
|
||||
|
||||
```cpp
|
||||
std::optional<xrpl::telemetry::SpanGuard> establishSpan_;
|
||||
```
|
||||
|
||||
- **Plan deviation**: `roundSpan_`, `prevRoundContext_`, and `roundSpanContext_`
|
||||
are stored in `RCLConsensus::Adaptor` (not `Consensus.h`) because the adaptor
|
||||
has access to telemetry config for the deterministic trace ID strategy.
|
||||
|
||||
- **No `#ifdef XRPL_ENABLE_TELEMETRY` guards**: Members use `std::optional<SpanGuard>`
|
||||
and `SpanContext` which have no-op implementations when telemetry is disabled,
|
||||
so `#ifdef` guards are unnecessary. The members are always present in the class
|
||||
layout but incur negligible overhead.
|
||||
|
||||
- Includes added unconditionally to `Consensus.h`:
|
||||
```cpp
|
||||
#include <xrpl/telemetry/SpanGuard.h>
|
||||
#include <xrpld/app/consensus/ConsensusSpanNames.h>
|
||||
```
|
||||
No `TracingInstrumentation.h` include (file doesn't exist; macros not used).
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/consensus/Consensus.h`
|
||||
- `src/xrpld/app/consensus/RCLConsensus.h` (round span and context members)
|
||||
|
||||
---
|
||||
|
||||
## Task 4a.4: Instrument `phaseEstablish()` ✅
|
||||
|
||||
**Objective**: Create `consensus.establish` span wrapping the establish phase,
|
||||
with attributes for convergence progress.
|
||||
|
||||
**Status**: Done. Implemented via three private helpers in `Consensus.h`.
|
||||
|
||||
**What was done**:
|
||||
|
||||
- `startEstablishTracing()` — creates `consensus.establish` span via
|
||||
`SpanGuard::span(TraceCategory::Consensus, seg::consensus, "establish")`.
|
||||
Called once at start of establish phase. No `#ifdef` guards needed —
|
||||
`SpanGuard::span()` returns a no-op guard when telemetry is disabled.
|
||||
|
||||
- `updateEstablishTracing()` — sets attributes on each `phaseEstablish()` call:
|
||||
- `converge_percent` — `convergePercent_`
|
||||
- `establish_count` — `establishCounter_`
|
||||
- `proposers` — `currPeerPositions_.size()`
|
||||
|
||||
- `endEstablishTracing()` — calls `establishSpan_.reset()` on phase exit.
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/consensus/Consensus.h` — `phaseEstablish()` method + 3 helper methods
|
||||
|
||||
---
|
||||
|
||||
## Task 4a.5: Instrument `updateOurPositions()` ✅
|
||||
|
||||
**Objective**: Trace each position update cycle including dispute resolution
|
||||
details.
|
||||
|
||||
**Status**: DONE. Span, dispute events with yays/nays, and disputes_count attribute are all implemented.
|
||||
|
||||
**What was done**:
|
||||
|
||||
- Creates `consensus.update_positions` scoped span via
|
||||
`SpanGuard::span(TraceCategory::Consensus, seg::consensus, "update_positions")`:
|
||||
|
||||
```cpp
|
||||
auto span = SpanGuard::span(TraceCategory::Consensus, seg::consensus, "update_positions");
|
||||
```
|
||||
|
||||
- Attributes set:
|
||||
- `converge_percent` — current convergence
|
||||
- `proposers` — `currPeerPositions_.size()`
|
||||
- `have_close_time_consensus` — close time consensus state
|
||||
- `close_time_threshold` — `avCT_CONSENSUS_PCT`
|
||||
- `disputes_count` — number of active disputes
|
||||
|
||||
- Dispute events recorded via direct `span.addEvent()` call with yays/nays:
|
||||
```cpp
|
||||
span.addEvent(
|
||||
"dispute.resolve",
|
||||
{{consensus::span::attr::txId, to_string(txId)},
|
||||
{consensus::span::attr::disputeOurVote, dispute.getOurVote() ? "yes" : "no"},
|
||||
{consensus::span::attr::disputeYays, std::to_string(dispute.getYays())},
|
||||
{consensus::span::attr::disputeNays, std::to_string(dispute.getNays())}});
|
||||
```
|
||||
|
||||
**Not implemented**:
|
||||
|
||||
- `proposers_agreed` / `proposers_total` attributes — not set
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/consensus/Consensus.h` — `updateOurPositions()` method
|
||||
- `src/xrpld/consensus/DisputedTx.h` — added `getYays()` / `getNays()` (currently unused)
|
||||
|
||||
---
|
||||
|
||||
## Task 4a.6: Instrument `haveConsensus()` (Threshold & Convergence) ✅
|
||||
|
||||
**Objective**: Trace consensus checking including threshold escalation.
|
||||
|
||||
**Status**: DONE. The `consensus.check` span is created with all planned attributes
|
||||
including the avalanche threshold.
|
||||
|
||||
**What was done**:
|
||||
|
||||
- Creates `consensus.check` scoped span via
|
||||
`SpanGuard::span(TraceCategory::Consensus, seg::consensus, "check")`:
|
||||
|
||||
```cpp
|
||||
auto span = SpanGuard::span(TraceCategory::Consensus, seg::consensus, "check");
|
||||
```
|
||||
|
||||
- Attributes set:
|
||||
- `agree_count` — peers that agree with our position
|
||||
- `disagree_count` — peers that disagree
|
||||
- `converge_percent` — convergence percentage
|
||||
- `have_close_time_consensus` — close time consensus state
|
||||
- `threshold_percent` — set to `avCT_CONSENSUS_PCT` (75%)
|
||||
- `consensus_result` — "yes", "no", or "moved_on"
|
||||
- `avalanche_threshold` — the escalated weight from `getNeededWeight()` on the `consensus.update_positions` span
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/consensus/Consensus.h` — `haveConsensus()` method
|
||||
|
||||
---
|
||||
|
||||
## Task 4a.7: Instrument Mode Changes ✅
|
||||
|
||||
**Objective**: Trace consensus mode transitions (proposing ↔ observing,
|
||||
wrongLedger, switchedLedger).
|
||||
|
||||
**Status**: Done.
|
||||
|
||||
**What was done**:
|
||||
|
||||
- In `RCLConsensus::Adaptor::onModeChange()`, creates a scoped span via direct
|
||||
`SpanGuard::span()` call:
|
||||
|
||||
```cpp
|
||||
auto span = telemetry::SpanGuard::span(
|
||||
telemetry::TraceCategory::Consensus, telemetry::seg::consensus, "mode_change");
|
||||
span.setAttribute(consensus::span::attr::modeOld, to_string(before).c_str()); // "mode_old"
|
||||
span.setAttribute(consensus::span::attr::modeNew, to_string(after).c_str()); // "mode_new"
|
||||
```
|
||||
|
||||
- `MonitoredMode::set()` in `Consensus.h` calls `adaptor_.onModeChange(before, after)`.
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/app/consensus/RCLConsensus.cpp` — `onModeChange()`
|
||||
|
||||
---
|
||||
|
||||
## Task 4a.8: Reparent Existing Spans Under Round ✅
|
||||
|
||||
**Objective**: Make existing consensus spans (`consensus.accept`,
|
||||
`consensus.accept.apply`, `consensus.validation.send`) children of the
|
||||
`consensus.round` root span instead of being standalone.
|
||||
|
||||
**Status**: DONE. All three spans are now parented under the round span.
|
||||
|
||||
**What was done**:
|
||||
|
||||
- `consensus.validation.send` uses `SpanGuard::linkedSpan()` to create a
|
||||
follows-from link to `roundSpanContext_`. This is thread-safe because
|
||||
`roundSpanContext_` is a lightweight `SpanContext` snapshot captured on the
|
||||
consensus thread and read on the jtACCEPT worker thread.
|
||||
|
||||
- `consensus.accept` and `consensus.accept.apply` now use
|
||||
`SpanGuard::childSpan(name, roundSpanContext_)` instead of `SpanGuard::span()`
|
||||
to explicitly parent under the round span context. This solves the cross-thread
|
||||
parenting problem:
|
||||
- `doAccept()` runs on the jtACCEPT worker thread (not the consensus thread)
|
||||
- `childSpan()` explicitly passes the parent context, bypassing OTel's
|
||||
thread-local context propagation
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/app/consensus/RCLConsensus.cpp`
|
||||
|
||||
---
|
||||
|
||||
## Task 4a.9: Build Verification and Testing ✅
|
||||
|
||||
**Objective**: Verify all Phase 4a changes compile cleanly with telemetry ON
|
||||
and OFF, and don't affect consensus timing.
|
||||
|
||||
**What to do**:
|
||||
|
||||
1. Build with `telemetry=ON` — verify no compilation errors
|
||||
2. Build with `telemetry=OFF` — verify `SpanGuard` compiles to no-ops
|
||||
3. Run existing consensus unit tests
|
||||
4. Verify `SpanGuard` / `SpanContext` members have negligible overhead when disabled
|
||||
5. Run `pccl` pre-commit checks
|
||||
|
||||
**Verification Checklist**:
|
||||
|
||||
- [x] Build succeeds with telemetry ON
|
||||
- [x] Build succeeds with telemetry OFF
|
||||
- [x] Existing consensus tests pass
|
||||
- [x] `SpanGuard` no-op path verified (no `#ifdef` needed — disabled at runtime)
|
||||
- [x] No new virtual calls in hot consensus paths
|
||||
- [x] `pccl` passes
|
||||
|
||||
---
|
||||
|
||||
## Phase 4a Summary
|
||||
|
||||
| Task | Description | Status | New Files | Modified Files | Depends On |
|
||||
| ---- | ------------------------------------------------ | ------------------------ | --------- | -------------- | ---------- |
|
||||
| 4a.0 | Prerequisites: extend SpanGuard & Telemetry APIs | ✅ Done (no macros) | 0 | 2 | Phase 4 |
|
||||
| 4a.1 | Adaptor `getTelemetry()` method | ⏭️ Skipped (not needed) | 0 | 0 | Phase 4 |
|
||||
| 4a.2 | Switchable round span with deterministic traceID | ✅ Done | 1 | 3 | 4a.0 |
|
||||
| 4a.3 | Span members in `Consensus.h` | ✅ Done (with deviation) | 0 | 2 | — |
|
||||
| 4a.4 | Instrument `phaseEstablish()` | ✅ Done | 0 | 1 | 4a.3 |
|
||||
| 4a.5 | Instrument `updateOurPositions()` | ✅ Done | 0 | 2 | 4a.0, 4a.3 |
|
||||
| 4a.6 | Instrument `haveConsensus()` (thresholds) | ✅ Done | 0 | 1 | 4a.3 |
|
||||
| 4a.7 | Instrument mode changes | ✅ Done | 0 | 1 | — |
|
||||
| 4a.8 | Reparent existing spans under round | ✅ Done | 0 | 1 | 4a.0, 4a.2 |
|
||||
| 4a.9 | Build verification and testing | ✅ Done | 0 | 0 | 4a.0-4a.8 |
|
||||
|
||||
**Parallel work**: Tasks 4a.0 and 4a.1 can run in parallel. Tasks 4a.4, 4a.5, 4a.6, and 4a.7 can run in parallel after 4a.3 (and 4a.0 for 4a.5).
|
||||
|
||||
### New Spans (Phase 4a)
|
||||
|
||||
| Span Name | Location | Key Attributes (actually set) |
|
||||
| ---------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `consensus.round` | `RCLConsensus.cpp` | `xrpl.consensus.round_id`, `xrpl.consensus.ledger_id`, `xrpl.ledger.seq`, `xrpl.consensus.mode`, `trace_strategy` |
|
||||
| `consensus.establish` | `Consensus.h` | `converge_percent`, `establish_count`, `proposers` |
|
||||
| `consensus.update_positions` | `Consensus.h` | `converge_percent`, `proposers`, `have_close_time_consensus`, `close_time_threshold`, `disputes_count`, `avalanche_threshold` |
|
||||
| `consensus.check` | `Consensus.h` | `agree_count`, `disagree_count`, `converge_percent`, `have_close_time_consensus`, `threshold_percent`, `consensus_result` |
|
||||
| `consensus.mode_change` | `RCLConsensus.cpp` | `mode_old`, `mode_new` |
|
||||
|
||||
### New Events (Phase 4a)
|
||||
|
||||
| Event Name | Parent Span | Attributes (actually set) |
|
||||
| ----------------- | ---------------------------- | ---------------------------------------------------------------- |
|
||||
| `dispute.resolve` | `consensus.update_positions` | `xrpl.tx.id`, `dispute_our_vote`, `dispute_yays`, `dispute_nays` |
|
||||
| `tx.included` | `consensus.accept.apply` | `xrpl.tx.id` |
|
||||
|
||||
### New Attributes (Phase 4a)
|
||||
|
||||
```cpp
|
||||
// Round-level (on consensus.round) — ALL IMPLEMENTED
|
||||
"xrpl.consensus.round_id" = int64 // Consensus round number
|
||||
"xrpl.consensus.ledger_id" = string // previousLedger.id() hash
|
||||
"trace_strategy" = string // "deterministic" or "attribute"
|
||||
|
||||
// Establish-level — IMPLEMENTED
|
||||
"converge_percent" = int64 // Convergence % (0-100+)
|
||||
"establish_count" = int64 // Number of establish iterations
|
||||
"agree_count" = int64 // Peers that agree (haveConsensus)
|
||||
"disagree_count" = int64 // Peers that disagree
|
||||
"threshold_percent" = int64 // Current threshold (avCT_CONSENSUS_PCT = 75%)
|
||||
"consensus_result" = string // "yes", "no", "moved_on"
|
||||
"have_close_time_consensus" = bool // Close time consensus reached
|
||||
"close_time_threshold" = int64 // Close time voting threshold
|
||||
|
||||
// Establish-level — IMPLEMENTED
|
||||
"disputes_count" = int64 // Active disputes (on update_positions)
|
||||
"avalanche_threshold" = int64 // Escalated weight (on update_positions)
|
||||
|
||||
// Establish-level — NOT IMPLEMENTED
|
||||
// "proposers_agreed" = int64 // Peers agreeing with us — not set
|
||||
// "proposers_total" = int64 // Total peer positions — not set (not defined)
|
||||
|
||||
// Mode change — ALL IMPLEMENTED
|
||||
"mode_old" = string // Previous mode
|
||||
"mode_new" = string // New mode
|
||||
```
|
||||
|
||||
### Implementation Notes
|
||||
|
||||
- **No macros**: The planned `XRPL_TRACE_CONSENSUS`, `XRPL_TRACE_ADD_EVENT`, and
|
||||
`XRPL_TRACE_SET_ATTR` macros were not implemented. All consensus tracing uses
|
||||
`SpanGuard` factory methods (`span()`, `hashSpan()`, `linkedSpan()`) and direct
|
||||
method calls (`setAttribute()`, `addEvent()`). This avoids macro control-flow
|
||||
issues and is cleaner than the planned approach.
|
||||
- **Separation of concerns**: All non-trivial telemetry code extracted to private
|
||||
helpers (`startRoundTracing`, `createValidationSpan`, `startEstablishTracing`,
|
||||
`updateEstablishTracing`, `endEstablishTracing`). Business logic methods contain
|
||||
single-line calls to these helpers.
|
||||
- **Thread safety**: `createValidationSpan()` runs on the jtACCEPT worker thread.
|
||||
Instead of accessing `roundSpan_` across threads, a `roundSpanContext_` snapshot
|
||||
(lightweight `SpanContext` value type) is captured on the consensus thread in
|
||||
`startRoundTracing()` and read by `createValidationSpan()`. The job queue
|
||||
provides the happens-before guarantee.
|
||||
- **No `#ifdef` guards**: Span members use `std::optional<SpanGuard>` and `SpanContext`
|
||||
which have no-op implementations when telemetry is disabled. No `#ifdef XRPL_ENABLE_TELEMETRY`
|
||||
guards needed around members or includes.
|
||||
- **No `getTelemetry()` adaptor method**: `SpanGuard::span()` is a static factory that
|
||||
internally checks telemetry state, so `Consensus.h` doesn't need adaptor access
|
||||
for span creation. Only `RCLConsensus::Adaptor` accesses `app_.getTelemetry()` directly.
|
||||
- **Config validation**: `consensus_trace_strategy` is validated to be either
|
||||
`"deterministic"` or `"attribute"`, falling back to `"deterministic"` for
|
||||
unrecognised values.
|
||||
- **Plan deviation**: `roundSpan_` is stored in `RCLConsensus::Adaptor` (not
|
||||
`Consensus.h`) because the adaptor has access to telemetry config and can
|
||||
implement the deterministic trace ID strategy. `establishSpan_` is correctly
|
||||
in `Consensus.h` as planned.
|
||||
|
||||
---
|
||||
|
||||
# Phase 4b: Cross-Node Propagation (Future — Documentation Only)
|
||||
|
||||
> **Goal**: Wire `TraceContextPropagator` for P2P messages so that proposals
|
||||
> and validations carry trace context between nodes. This enables true
|
||||
> distributed tracing where a proposal sent by Node A creates a child span
|
||||
> on Node B.
|
||||
>
|
||||
> **Status**: NOT IMPLEMENTED. The protobuf fields and propagator class exist
|
||||
> but are not wired. This section documents the design for future work.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Node A (proposing) Node B (receiving)
|
||||
───────────────── ──────────────────
|
||||
consensus.round consensus.round
|
||||
├── propose() ├── peerProposal()
|
||||
│ └── TraceContextPropagator │ └── TraceContextPropagator
|
||||
│ ::injectToProtobuf( │ ::extractFromProtobuf(
|
||||
│ TMProposeSet.trace_context) │ TMProposeSet.trace_context)
|
||||
│ │ └── span link → Node A's context
|
||||
└── validate() └── onValidation()
|
||||
└── inject into TMValidation └── extract from TMValidation
|
||||
```
|
||||
|
||||
## Wiring Points
|
||||
|
||||
| Message | Inject Location | Extract Location | Protobuf Field |
|
||||
| --------------- | ---------------------------------- | ----------------------------------- | -------------------------- |
|
||||
| `TMProposeSet` | `Adaptor::propose()` | `PeerImp::onMessage(TMProposeSet)` | field 1001: `TraceContext` |
|
||||
| `TMValidation` | `Adaptor::validate()` | `PeerImp::onMessage(TMValidation)` | field 1001: `TraceContext` |
|
||||
| `TMTransaction` | `NetworkOPs::processTransaction()` | `PeerImp::onMessage(TMTransaction)` | field 1001: `TraceContext` |
|
||||
|
||||
## Span Link Semantics
|
||||
|
||||
Received messages use **span links** (follows-from), NOT parent-child:
|
||||
|
||||
- The receiver's processing span links to the sender's context
|
||||
- This preserves each node's independent trace tree
|
||||
- Cross-node correlation visible via linked traces in Tempo
|
||||
|
||||
## Interaction with Deterministic Trace ID (Strategy A)
|
||||
|
||||
When using deterministic trace_id (Phase 4a default), cross-node spans already
|
||||
share the same trace_id. P2P propagation adds **span-level** linking:
|
||||
|
||||
- Without propagation: spans from different nodes appear in the same trace
|
||||
(same trace_id) but without parent-child or follows-from relationships.
|
||||
- With propagation: spans have explicit links showing which proposal/validation
|
||||
from Node A caused processing on Node B.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Phase 4a (this task list) — establish phase tracing must be in place
|
||||
- `TraceContextPropagator` free functions (already exist in
|
||||
`include/xrpl/telemetry/TraceContextPropagator.h`)
|
||||
- Protobuf `TraceContext` message (already exists, field 1001)
|
||||
221
OpenTelemetryPlan/Phase5_IntegrationTest_taskList.md
Normal file
221
OpenTelemetryPlan/Phase5_IntegrationTest_taskList.md
Normal file
@@ -0,0 +1,221 @@
|
||||
# Phase 5: Integration Test Task List
|
||||
|
||||
> **Goal**: End-to-end verification of the complete telemetry pipeline using a
|
||||
> 6-node consensus network. Proves that RPC, transaction, and consensus spans
|
||||
> flow through the observability stack (otel-collector, Tempo, Prometheus,
|
||||
> Grafana) under realistic conditions.
|
||||
>
|
||||
> **Scope**: Integration test script, manual testing plan, 6-node local network
|
||||
> setup, Tempo/Prometheus/Grafana verification.
|
||||
>
|
||||
> **Branch**: `pratik/otel-phase5-docs-deployment`
|
||||
|
||||
### Related Plan Documents
|
||||
|
||||
| Document | Relevance |
|
||||
| ---------------------------------------------------------------- | ------------------------------------------ |
|
||||
| [07-observability-backends.md](./07-observability-backends.md) | Tempo, Grafana, Prometheus setup |
|
||||
| [05-configuration-reference.md](./05-configuration-reference.md) | Collector config, Docker Compose |
|
||||
| [06-implementation-phases.md](./06-implementation-phases.md) | Phase 5 tasks, definition of done |
|
||||
| [Phase5_taskList.md](./Phase5_taskList.md) | Phase 5 main task list (5.6 = integration) |
|
||||
|
||||
---
|
||||
|
||||
## Task IT.1: Create Integration Test Script
|
||||
|
||||
**Objective**: Automated bash script that stands up a 6-node xrpld network
|
||||
with telemetry, exercises all span categories, and verifies data in
|
||||
Tempo/Prometheus.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Create `docker/telemetry/integration-test.sh`:
|
||||
- Prerequisites check (docker, xrpld binary, curl, jq)
|
||||
- Start observability stack via `docker compose`
|
||||
- Generate 6 validator key pairs via temp standalone xrpld
|
||||
- Generate 6 node configs + shared `validators.txt`
|
||||
- Start 6 xrpld nodes in consensus mode (`--start`, no `-a`)
|
||||
- Wait for all nodes to reach `"proposing"` state (120s timeout)
|
||||
|
||||
**Key new file**: `docker/telemetry/integration-test.sh`
|
||||
|
||||
**Verification**:
|
||||
|
||||
- [ ] Script starts without errors
|
||||
- [ ] All 6 nodes reach "proposing" state
|
||||
- [ ] Observability stack is healthy (otel-collector, Tempo, Prometheus, Grafana)
|
||||
|
||||
---
|
||||
|
||||
## Task IT.2: RPC Span Verification (Phase 2)
|
||||
|
||||
**Objective**: Verify RPC spans flow through the telemetry pipeline.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Send `server_info`, `server_state`, `ledger` RPCs to node1 (port 5005)
|
||||
- Wait for batch export (5s)
|
||||
- Query Tempo API for:
|
||||
- `rpc.request` spans (ServerHandler::onRequest)
|
||||
- `rpc.process` spans (ServerHandler::processRequest)
|
||||
- `rpc.command.server_info` spans (callMethod)
|
||||
- `rpc.command.server_state` spans (callMethod)
|
||||
- `rpc.command.ledger` spans (callMethod)
|
||||
- Verify `command` attribute present on `rpc.command.*` spans
|
||||
|
||||
**Verification**:
|
||||
|
||||
- [ ] Tempo shows `rpc.request` traces
|
||||
- [ ] Tempo shows `rpc.process` traces
|
||||
- [ ] Tempo shows `rpc.command.*` traces with correct attributes
|
||||
|
||||
---
|
||||
|
||||
## Task IT.3: Transaction Span Verification (Phase 3)
|
||||
|
||||
**Objective**: Verify transaction spans flow through the telemetry pipeline.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Get genesis account sequence via `account_info` RPC
|
||||
- Submit Payment transaction using genesis seed (`snoPBrXtMeMyMHUVTgbuqAfg1SUTb`)
|
||||
- Wait for consensus inclusion (10s)
|
||||
- Query Tempo API for:
|
||||
- `tx.process` spans (NetworkOPsImp::processTransaction) on submitting node
|
||||
- `tx.receive` spans (PeerImp::handleTransaction) on peer nodes
|
||||
- Verify `xrpl.tx.hash` attribute on `tx.process` spans
|
||||
- Verify `xrpl.peer.id` attribute on `tx.receive` spans
|
||||
|
||||
**Verification**:
|
||||
|
||||
- [ ] Tempo shows `tx.process` traces with `xrpl.tx.hash`
|
||||
- [ ] Tempo shows `tx.receive` traces with `xrpl.peer.id`
|
||||
|
||||
---
|
||||
|
||||
## Task IT.4: Consensus Span Verification (Phase 4)
|
||||
|
||||
**Objective**: Verify consensus spans flow through the telemetry pipeline.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Consensus runs automatically in 6-node network
|
||||
- Query Tempo API for:
|
||||
- `consensus.proposal.send` (Adaptor::propose)
|
||||
- `consensus.ledger_close` (Adaptor::onClose)
|
||||
- `consensus.accept` (Adaptor::onAccept)
|
||||
- `consensus.validation.send` (Adaptor::validate)
|
||||
- Verify attributes:
|
||||
- `xrpl.consensus.mode` on `consensus.ledger_close`
|
||||
- `xrpl.consensus.proposers` on `consensus.accept`
|
||||
- `xrpl.consensus.ledger.seq` on `consensus.validation.send`
|
||||
|
||||
**Verification**:
|
||||
|
||||
- [ ] Tempo shows `consensus.ledger_close` traces with `xrpl.consensus.mode`
|
||||
- [ ] Tempo shows `consensus.accept` traces with `xrpl.consensus.proposers`
|
||||
- [ ] Tempo shows `consensus.proposal.send` traces
|
||||
- [ ] Tempo shows `consensus.validation.send` traces
|
||||
|
||||
---
|
||||
|
||||
## Task IT.5: Spanmetrics Verification (Phase 5)
|
||||
|
||||
**Objective**: Verify spanmetrics connector derives RED metrics from spans.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Query Prometheus for `traces_span_metrics_calls_total`
|
||||
- Query Prometheus for `traces_span_metrics_duration_milliseconds_count`
|
||||
- Verify Grafana loads at `http://localhost:3000`
|
||||
|
||||
**Verification**:
|
||||
|
||||
- [ ] Prometheus returns non-empty results for `traces_span_metrics_calls_total`
|
||||
- [ ] Prometheus returns non-empty results for duration histogram
|
||||
- [ ] Grafana UI accessible with dashboards visible
|
||||
|
||||
---
|
||||
|
||||
## Task IT.6: Manual Testing Plan
|
||||
|
||||
**Objective**: Document how to run tests manually for future reference.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Create `docker/telemetry/TESTING.md` with:
|
||||
- Prerequisites section
|
||||
- Single-node standalone test (quick verification)
|
||||
- 6-node consensus test (full verification)
|
||||
- Expected span catalog (all 12 span names with attributes)
|
||||
- Verification queries (Tempo API, Prometheus API)
|
||||
- Troubleshooting guide
|
||||
|
||||
**Key new file**: `docker/telemetry/TESTING.md`
|
||||
|
||||
**Verification**:
|
||||
|
||||
- [ ] Document covers both single-node and multi-node testing
|
||||
- [ ] All 12 span names documented with source file and attributes
|
||||
- [ ] Troubleshooting section covers common failure modes
|
||||
|
||||
---
|
||||
|
||||
## Task IT.7: Run and Verify
|
||||
|
||||
**Objective**: Execute the integration test and validate results.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Run `docker/telemetry/integration-test.sh` locally
|
||||
- Debug any failures
|
||||
- Leave stack running for manual verification
|
||||
- Share URLs:
|
||||
- Tempo: `http://localhost:3200`
|
||||
- Grafana: `http://localhost:3000`
|
||||
- Prometheus: `http://localhost:9090`
|
||||
|
||||
**Verification**:
|
||||
|
||||
- [ ] Script completes with all checks passing
|
||||
- [ ] Tempo UI shows xrpld service with all expected span names
|
||||
- [ ] Grafana dashboards load and show data
|
||||
|
||||
---
|
||||
|
||||
## Task IT.8: Commit
|
||||
|
||||
**Objective**: Commit all new files to Phase 5 branch.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Run `pcc` (pre-commit checks)
|
||||
- Commit 3 new files to `pratik/otel-phase5-docs-deployment`
|
||||
|
||||
**Verification**:
|
||||
|
||||
- [ ] `pcc` passes
|
||||
- [ ] Commit created on Phase 5 branch
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Task | Description | New Files | Depends On |
|
||||
| ---- | ----------------------------- | --------- | ---------- |
|
||||
| IT.1 | Integration test script | 1 | Phase 5 |
|
||||
| IT.2 | RPC span verification | 0 | IT.1 |
|
||||
| IT.3 | Transaction span verification | 0 | IT.1 |
|
||||
| IT.4 | Consensus span verification | 0 | IT.1 |
|
||||
| IT.5 | Spanmetrics verification | 0 | IT.1 |
|
||||
| IT.6 | Manual testing plan | 1 | -- |
|
||||
| IT.7 | Run and verify | 0 | IT.1-IT.6 |
|
||||
| IT.8 | Commit | 0 | IT.7 |
|
||||
|
||||
**Exit Criteria**:
|
||||
|
||||
- [ ] All 6 xrpld nodes reach "proposing" state
|
||||
- [ ] All 11 expected span names visible in Tempo
|
||||
- [ ] Spanmetrics available in Prometheus
|
||||
- [ ] Grafana dashboards show data
|
||||
- [ ] Manual testing plan document complete
|
||||
250
OpenTelemetryPlan/Phase5_taskList.md
Normal file
250
OpenTelemetryPlan/Phase5_taskList.md
Normal file
@@ -0,0 +1,250 @@
|
||||
# Phase 5: Documentation & Deployment Task List
|
||||
|
||||
> **Goal**: Production readiness — Grafana dashboards, spanmetrics pipeline, operator runbook, alert definitions, and final integration testing. This phase ensures the telemetry system is useful and maintainable in production.
|
||||
>
|
||||
> **Scope**: Grafana dashboard definitions, OTel Collector spanmetrics connector, Prometheus integration, alert rules, operator documentation, and production-ready Docker Compose stack.
|
||||
>
|
||||
> **Branch**: `pratik/otel-phase5-docs-deployment` (from `pratik/otel-phase4-consensus-tracing`)
|
||||
|
||||
> **Note on attribute names**: the `xrpl.<domain>.<field>` keys shown below
|
||||
> (including the collector spanmetrics dimension examples) are written in the
|
||||
> older dotted form for readability — it mirrors how the fully qualified
|
||||
> attribute reads in a Tempo trace view. The implemented keys follow the
|
||||
> convention in [CONTRIBUTING.md](../CONTRIBUTING.md#telemetry-span-attribute-naming)
|
||||
> (underscore form, e.g. `command`, `rpc_status`); the `*SpanNames.h` constants
|
||||
> are the single source of truth, and the real collector dimensions must use
|
||||
> those exact underscore keys (the CI naming check enforces this).
|
||||
|
||||
### Related Plan Documents
|
||||
|
||||
| Document | Relevance |
|
||||
| ---------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
||||
| [07-observability-backends.md](./07-observability-backends.md) | Tempo setup (§7.1), Grafana dashboards (§7.6), alerts (§7.6.3) |
|
||||
| [05-configuration-reference.md](./05-configuration-reference.md) | Collector config (§5.5), production config (§5.5.2), Docker Compose (§5.6) |
|
||||
| [06-implementation-phases.md](./06-implementation-phases.md) | Phase 5 tasks (§6.6), definition of done (§6.11.5) |
|
||||
|
||||
---
|
||||
|
||||
## Task 5.1: Add Spanmetrics Connector to OTel Collector
|
||||
|
||||
**Objective**: Derive RED metrics (Rate, Errors, Duration) from trace spans automatically, enabling Grafana time-series dashboards.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Edit `docker/telemetry/otel-collector-config.yaml`:
|
||||
- Add `spanmetrics` connector:
|
||||
```yaml
|
||||
connectors:
|
||||
spanmetrics:
|
||||
histogram:
|
||||
explicit:
|
||||
buckets: [1ms, 5ms, 10ms, 25ms, 50ms, 100ms, 250ms, 500ms, 1s, 5s]
|
||||
dimensions:
|
||||
- name: command
|
||||
- name: rpc_status
|
||||
- name: consensus_phase
|
||||
- name: tx_type
|
||||
```
|
||||
- Add `prometheus` exporter:
|
||||
```yaml
|
||||
exporters:
|
||||
prometheus:
|
||||
endpoint: 0.0.0.0:8889
|
||||
```
|
||||
- Wire the pipeline:
|
||||
```yaml
|
||||
service:
|
||||
pipelines:
|
||||
traces:
|
||||
receivers: [otlp]
|
||||
processors: [batch]
|
||||
exporters: [debug, otlp/tempo, spanmetrics]
|
||||
metrics:
|
||||
receivers: [spanmetrics]
|
||||
exporters: [prometheus]
|
||||
```
|
||||
|
||||
- Edit `docker/telemetry/docker-compose.yml`:
|
||||
- Expose port `8889` on the collector for Prometheus scraping
|
||||
- Add Prometheus service
|
||||
- Add Prometheus as Grafana datasource
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `docker/telemetry/otel-collector-config.yaml`
|
||||
- `docker/telemetry/docker-compose.yml`
|
||||
|
||||
**Key new files**:
|
||||
|
||||
- `docker/telemetry/prometheus.yml` (Prometheus scrape config)
|
||||
- `docker/telemetry/grafana/provisioning/datasources/prometheus.yaml`
|
||||
|
||||
**Reference**:
|
||||
|
||||
- [POC_taskList.md §Next Steps](./POC_taskList.md) — Metrics pipeline for Grafana dashboards
|
||||
|
||||
---
|
||||
|
||||
## Task 5.2: Create Grafana Dashboards
|
||||
|
||||
**Objective**: Provide pre-built Grafana dashboards for RPC performance, transaction lifecycle, and consensus health.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Create `docker/telemetry/grafana/provisioning/dashboards/dashboards.yaml` (provisioning config)
|
||||
- Create dashboard JSON files:
|
||||
1. **RPC Performance Dashboard** (`rpc-performance.json`):
|
||||
- RPC request latency (p50/p95/p99) by command — histogram panel
|
||||
- RPC throughput (requests/sec) by command — time series
|
||||
- RPC error rate by command — bar gauge
|
||||
- Top slowest RPC commands — table
|
||||
|
||||
2. **Transaction Overview Dashboard** (`transaction-overview.json`):
|
||||
- Transaction processing rate — time series
|
||||
- Transaction latency distribution — histogram
|
||||
- Suppression rate (duplicates) — stat panel
|
||||
- Transaction processing path (sync vs async) — pie chart
|
||||
|
||||
3. **Consensus Health Dashboard** (`consensus-health.json`):
|
||||
- Consensus round duration — time series
|
||||
- Phase duration breakdown (open/establish/accept) — stacked bar
|
||||
- Proposals sent/received per round — stat panel
|
||||
- Consensus mode distribution (proposing/observing) — pie chart
|
||||
|
||||
- Store dashboards in `docker/telemetry/grafana/dashboards/`
|
||||
|
||||
**Key new files**:
|
||||
|
||||
- `docker/telemetry/grafana/provisioning/dashboards/dashboards.yaml`
|
||||
- `docker/telemetry/grafana/dashboards/rpc-performance.json`
|
||||
- `docker/telemetry/grafana/dashboards/transaction-overview.json`
|
||||
- `docker/telemetry/grafana/dashboards/consensus-health.json`
|
||||
|
||||
**Reference**:
|
||||
|
||||
- [07-observability-backends.md §7.6](./07-observability-backends.md) — Grafana dashboard specifications
|
||||
- [01-architecture-analysis.md §1.8.3](./01-architecture-analysis.md) — Dashboard panel examples
|
||||
|
||||
---
|
||||
|
||||
## Task 5.3: Define Alert Rules
|
||||
|
||||
**Objective**: Create alert definitions for key telemetry anomalies.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Create `docker/telemetry/grafana/provisioning/alerting/alerts.yaml`:
|
||||
- **RPC Latency Alert**: p99 latency > 1s for any command over 5 minutes
|
||||
- **RPC Error Rate Alert**: Error rate > 5% for any command over 5 minutes
|
||||
- **Consensus Duration Alert**: Round duration > 10s (warn), > 30s (critical)
|
||||
- **Transaction Processing Alert**: Processing rate drops below threshold
|
||||
- **Telemetry Pipeline Health**: No spans received for > 2 minutes
|
||||
|
||||
**Key new files**:
|
||||
|
||||
- `docker/telemetry/grafana/provisioning/alerting/alerts.yaml`
|
||||
|
||||
**Reference**:
|
||||
|
||||
- [07-observability-backends.md §7.6.3](./07-observability-backends.md) — Alert rule definitions
|
||||
|
||||
---
|
||||
|
||||
## Task 5.4: Production Collector Configuration
|
||||
|
||||
**Objective**: Create a production-ready OTel Collector configuration with tail-based sampling and resource limits.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Create `docker/telemetry/otel-collector-config-production.yaml`:
|
||||
- Tail-based sampling policy:
|
||||
- Always sample errors and slow traces
|
||||
- 10% base sampling rate for normal traces
|
||||
- Always sample first trace for each unique RPC command
|
||||
- Resource limits:
|
||||
- Memory limiter processor (80% of available memory)
|
||||
- Queued retry for export failures
|
||||
- TLS configuration for production endpoints
|
||||
- Health check endpoint
|
||||
|
||||
**Key new files**:
|
||||
|
||||
- `docker/telemetry/otel-collector-config-production.yaml`
|
||||
|
||||
**Reference**:
|
||||
|
||||
- [05-configuration-reference.md §5.5.2](./05-configuration-reference.md) — Production collector config
|
||||
|
||||
---
|
||||
|
||||
## Task 5.5: Operator Runbook
|
||||
|
||||
**Objective**: Create operator documentation for managing the telemetry system in production.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Create `docs/telemetry-runbook.md`:
|
||||
- **Setup**: How to enable telemetry in xrpld
|
||||
- **Configuration**: All config options with descriptions
|
||||
- **Collector Deployment**: Docker Compose vs. Kubernetes vs. bare metal
|
||||
- **Troubleshooting**: Common issues and resolutions
|
||||
- No traces appearing
|
||||
- High memory usage from telemetry
|
||||
- Collector connection failures
|
||||
- Sampling configuration tuning
|
||||
- **Performance Tuning**: Batch size, queue size, sampling ratio guidelines
|
||||
- **Upgrading**: How to upgrade OTel SDK and Collector versions
|
||||
|
||||
**Key new files**:
|
||||
|
||||
- `docs/telemetry-runbook.md`
|
||||
|
||||
---
|
||||
|
||||
## Task 5.6: Final Integration Testing
|
||||
|
||||
**Objective**: Validate the complete telemetry stack end-to-end.
|
||||
|
||||
**What to do**:
|
||||
|
||||
1. Start full Docker stack (Collector, Tempo, Grafana, Prometheus)
|
||||
2. Build xrpld with `telemetry=ON`
|
||||
3. Run in standalone mode with telemetry enabled
|
||||
4. Generate RPC traffic and verify traces in Tempo
|
||||
5. Verify dashboards populate in Grafana
|
||||
6. Verify alerts trigger correctly
|
||||
7. Test telemetry OFF path (no regressions)
|
||||
8. Run full test suite
|
||||
|
||||
**Verification Checklist**:
|
||||
|
||||
- [ ] Docker stack starts without errors
|
||||
- [ ] Traces appear in Tempo with correct hierarchy
|
||||
- [ ] Grafana dashboards show metrics derived from spans
|
||||
- [ ] Prometheus scrapes spanmetrics successfully
|
||||
- [ ] Alerts can be triggered by simulated conditions
|
||||
- [ ] Build succeeds with telemetry ON and OFF
|
||||
- [ ] Full test suite passes
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Task | Description | New Files | Modified Files | Depends On |
|
||||
| ---- | ---------------------------------- | --------- | -------------- | ---------- |
|
||||
| 5.1 | Spanmetrics connector + Prometheus | 2 | 2 | Phase 4 |
|
||||
| 5.2 | Grafana dashboards | 4 | 0 | 5.1 |
|
||||
| 5.3 | Alert definitions | 1 | 0 | 5.1 |
|
||||
| 5.4 | Production collector config | 1 | 0 | Phase 4 |
|
||||
| 5.5 | Operator runbook | 1 | 0 | Phase 4 |
|
||||
| 5.6 | Final integration testing | 0 | 0 | 5.1-5.5 |
|
||||
|
||||
**Parallel work**: Tasks 5.1, 5.4, and 5.5 can run in parallel. Tasks 5.2 and 5.3 depend on 5.1. Task 5.6 depends on all others.
|
||||
|
||||
**Exit Criteria** (from [06-implementation-phases.md §6.11.5](./06-implementation-phases.md)):
|
||||
|
||||
- [ ] Dashboards deployed and showing data
|
||||
- [ ] Alerts configured and tested
|
||||
- [ ] Operator documentation complete
|
||||
- [ ] Production collector config ready
|
||||
- [ ] Full test suite passes
|
||||
587
OpenTelemetryPlan/Phase7_taskList.md
Normal file
587
OpenTelemetryPlan/Phase7_taskList.md
Normal file
@@ -0,0 +1,587 @@
|
||||
# Phase 7: Native OTel Metrics Migration — Task List
|
||||
|
||||
> **Goal**: Replace `StatsDCollector` with a native OpenTelemetry Metrics SDK implementation behind the existing `beast::insight::Collector` interface, eliminating the StatsD UDP dependency.
|
||||
>
|
||||
> **Scope**: New `OTelCollectorImpl` class, `CollectorManager` config change, OTel Collector pipeline update, Grafana dashboard metric name migration, integration tests.
|
||||
>
|
||||
> **Branch**: `pratik/otel-phase7-native-metrics` (from `pratik/otel-phase6-statsd`)
|
||||
|
||||
### Related Plan Documents
|
||||
|
||||
| Document | Relevance |
|
||||
| -------------------------------------------------------------------- | --------------------------------------------------------------- |
|
||||
| [06-implementation-phases.md](./06-implementation-phases.md) | Phase 7 plan: motivation, architecture, exit criteria (§6.8) |
|
||||
| [02-design-decisions.md](./02-design-decisions.md) | Collector interface design, beast::insight coexistence strategy |
|
||||
| [05-configuration-reference.md](./05-configuration-reference.md) | `[insight]` and `[telemetry]` config sections |
|
||||
| [09-data-collection-reference.md](./09-data-collection-reference.md) | Complete metric inventory that must be preserved |
|
||||
|
||||
---
|
||||
|
||||
## Task 7.1: Add OTel Metrics SDK to Build Dependencies
|
||||
|
||||
**Objective**: Enable the OTel C++ Metrics SDK components in the build system.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Edit `conanfile.py`:
|
||||
- Add OTel metrics SDK components to the dependency list when `telemetry=True`
|
||||
- Components needed: `opentelemetry-cpp::metrics`, `opentelemetry-cpp::otlp_http_metric_exporter`
|
||||
|
||||
- Edit `CMakeLists.txt` (telemetry section):
|
||||
- Link `opentelemetry::metrics` and `opentelemetry::otlp_http_metric_exporter` targets
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `conanfile.py`
|
||||
- `CMakeLists.txt` (or the relevant telemetry cmake target)
|
||||
|
||||
**Reference**: [05-configuration-reference.md §5.3](./05-configuration-reference.md) — CMake integration
|
||||
|
||||
---
|
||||
|
||||
## Task 7.2: Implement OTelCollector Class
|
||||
|
||||
**Objective**: Create the core `OTelCollector` implementation that maps beast::insight instruments to OTel Metrics SDK instruments.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Create `include/xrpl/beast/insight/OTelCollector.h`:
|
||||
- Public factory: `static std::shared_ptr<OTelCollector> New(std::string const& endpoint, std::string const& prefix, beast::Journal journal)`
|
||||
- Derives from `StatsDCollector` (or directly from `Collector` — TBD based on shared code)
|
||||
|
||||
- Create `src/libxrpl/beast/insight/OTelCollector.cpp` (~400-500 lines):
|
||||
- **OTelCounterImpl**: Wraps `opentelemetry::metrics::Counter<int64_t>`. `increment(amount)` calls `counter->Add(amount)`.
|
||||
- **OTelGaugeImpl**: Uses `opentelemetry::metrics::ObservableGauge<uint64_t>` with an async callback. `set(value)` stores value atomically; callback reads it during collection.
|
||||
- **OTelMeterImpl**: Wraps `opentelemetry::metrics::Counter<uint64_t>`. `increment(amount)` calls `counter->Add(amount)`. Semantically identical to Counter but unsigned.
|
||||
- **OTelEventImpl**: Wraps `opentelemetry::metrics::Histogram<double>`. `notify(duration)` calls `histogram->Record(duration.count())`. Uses explicit bucket boundaries matching SpanMetrics: [1, 5, 10, 25, 50, 100, 250, 500, 1000, 5000] ms.
|
||||
- **OTelHookImpl**: Stores handler function. Called during periodic metric collection (same 1s pattern via PeriodicMetricReader).
|
||||
- **OTelCollectorImp**: Main class.
|
||||
- Creates `MeterProvider` with `PeriodicMetricReader` (1s export interval)
|
||||
- Creates `OtlpHttpMetricExporter` pointing to `[telemetry]` endpoint
|
||||
- Sets resource attributes (service.name, service.instance.id) matching trace exporter
|
||||
- Implements all `make_*()` factory methods
|
||||
- Prefixes metric names with `[insight] prefix=` value
|
||||
|
||||
- Guard all OTel SDK includes with `#ifdef XRPL_ENABLE_TELEMETRY` to compile to `NullCollector` equivalents when telemetry disabled.
|
||||
|
||||
**Key new files**:
|
||||
|
||||
- `include/xrpl/beast/insight/OTelCollector.h`
|
||||
- `src/libxrpl/beast/insight/OTelCollector.cpp`
|
||||
|
||||
**Key patterns to follow**:
|
||||
|
||||
- Match `StatsDCollector.cpp` structure: private impl classes, intrusive list for metrics, strand-based thread safety
|
||||
- Match existing telemetry code style from `src/libxrpl/telemetry/Telemetry.cpp`
|
||||
- Use RAII for MeterProvider lifecycle (shutdown on destructor)
|
||||
|
||||
**Reference**: [04-code-samples.md](./04-code-samples.md) — code style and patterns
|
||||
|
||||
---
|
||||
|
||||
## Task 7.3: Update CollectorManager
|
||||
|
||||
**Objective**: Add `server=otel` config option to route metric creation to the new OTel backend.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Edit `src/xrpld/app/main/CollectorManager.cpp`:
|
||||
- In the constructor, add a third branch after `server == "statsd"`:
|
||||
```cpp
|
||||
else if (server == "otel")
|
||||
{
|
||||
// Read endpoint from [telemetry] section
|
||||
auto const endpoint = get(telemetryParams, "endpoint",
|
||||
"http://localhost:4318/v1/metrics");
|
||||
std::string const& prefix(get(params, "prefix"));
|
||||
collector_ = beast::insight::OTelCollector::New(
|
||||
endpoint, prefix, journal);
|
||||
}
|
||||
```
|
||||
- This requires access to the `[telemetry]` config section — may need to pass it as a parameter or read from Application config.
|
||||
|
||||
- Edit `src/xrpld/app/main/CollectorManager.h`:
|
||||
- Add `#include <xrpl/beast/insight/OTelCollector.h>`
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/app/main/CollectorManager.cpp`
|
||||
- `src/xrpld/app/main/CollectorManager.h`
|
||||
|
||||
---
|
||||
|
||||
## Task 7.4: Update OTel Collector Configuration
|
||||
|
||||
**Objective**: Add a metrics pipeline to the OTLP receiver and remove the StatsD receiver dependency.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Edit `docker/telemetry/otel-collector-config.yaml`:
|
||||
- Remove `statsd` receiver (no longer needed when `server=otel`)
|
||||
- Add metrics pipeline under `service.pipelines`:
|
||||
```yaml
|
||||
metrics:
|
||||
receivers: [otlp, spanmetrics]
|
||||
processors: [batch]
|
||||
exporters: [prometheus]
|
||||
```
|
||||
- The OTLP receiver already listens on :4318 — it just needs to be added to the metrics pipeline receivers.
|
||||
- Keep `spanmetrics` connector in the metrics pipeline so span-derived RED metrics continue working.
|
||||
|
||||
- Edit `docker/telemetry/docker-compose.yml`:
|
||||
- Remove UDP :8125 port mapping from otel-collector service
|
||||
- Update xrpld service config: change `[insight] server=statsd` to `server=otel`
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `docker/telemetry/otel-collector-config.yaml`
|
||||
- `docker/telemetry/docker-compose.yml`
|
||||
|
||||
**Note**: Keep a commented-out `statsd` receiver block for operators who need backward compatibility.
|
||||
|
||||
---
|
||||
|
||||
## Task 7.5: Preserve Metric Names in Prometheus
|
||||
|
||||
**Objective**: Ensure existing Grafana dashboards continue working with identical metric names.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- In `OTelCollector.cpp`, construct OTel instrument names to match existing Prometheus metric names:
|
||||
- beast::insight `make_gauge("LedgerMaster", "Validated_Ledger_Age")` → OTel instrument name: `xrpld_LedgerMaster_Validated_Ledger_Age`
|
||||
- The prefix + group + name concatenation must produce the same string as `StatsDCollector`'s format
|
||||
- Use underscores as separators (matching StatsD convention)
|
||||
|
||||
- Verify in integration test that key Prometheus queries still return data:
|
||||
- `xrpld_LedgerMaster_Validated_Ledger_Age`
|
||||
- `xrpld_Peer_Finder_Active_Inbound_Peers`
|
||||
- `xrpld_rpc_requests`
|
||||
|
||||
**Key consideration**: OTel Prometheus exporter may normalize metric names differently than StatsD receiver. Test this early (Task 7.2) and adjust naming strategy if needed. The OTel SDK's Prometheus exporter adds `_total` suffix to counters and converts dots to underscores — match existing conventions.
|
||||
|
||||
---
|
||||
|
||||
## Task 7.6: Update Grafana Dashboards
|
||||
|
||||
**Objective**: Update the 3 StatsD dashboards if any metric names change due to OTLP export format differences.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- If Task 7.5 confirms metric names are preserved exactly, no dashboard changes needed.
|
||||
- If OTLP export produces different names (e.g., `_total` suffix on counters), update:
|
||||
- `docker/telemetry/grafana/dashboards/statsd-node-health.json`
|
||||
- `docker/telemetry/grafana/dashboards/statsd-network-traffic.json`
|
||||
- `docker/telemetry/grafana/dashboards/statsd-rpc-pathfinding.json`
|
||||
- Rename dashboard titles from "StatsD" to "System Metrics" or similar (since they're no longer StatsD-sourced).
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `docker/telemetry/grafana/dashboards/statsd-*.json` (3 files, conditionally)
|
||||
|
||||
---
|
||||
|
||||
## Task 7.7: Update Integration Tests
|
||||
|
||||
**Objective**: Verify the full OTLP metrics pipeline end-to-end.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Edit `docker/telemetry/integration-test.sh`:
|
||||
- Update test config to use `[insight] server=otel`
|
||||
- Verify metrics arrive in Prometheus via OTLP (not StatsD)
|
||||
- Add check that StatsD receiver is no longer required
|
||||
- Preserve all existing metric presence checks
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `docker/telemetry/integration-test.sh`
|
||||
|
||||
---
|
||||
|
||||
## Task 7.8: Update Documentation
|
||||
|
||||
**Objective**: Update all plan docs, runbook, and reference docs to reflect the migration.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Edit `docs/telemetry-runbook.md`:
|
||||
- Update `[insight]` config examples to show `server=otel`
|
||||
- Update troubleshooting section (no more StatsD UDP debugging)
|
||||
|
||||
- Edit `OpenTelemetryPlan/09-data-collection-reference.md`:
|
||||
- Update Data Flow Overview diagram (remove StatsD receiver)
|
||||
- Update Section 2 header from "StatsD Metrics" to "System Metrics (OTel native)"
|
||||
- Update config examples
|
||||
|
||||
- Edit `OpenTelemetryPlan/05-configuration-reference.md`:
|
||||
- Add `server=otel` option to `[insight]` section docs
|
||||
|
||||
- Edit `docker/telemetry/TESTING.md`:
|
||||
- Update setup instructions to use `server=otel`
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `docs/telemetry-runbook.md`
|
||||
- `OpenTelemetryPlan/09-data-collection-reference.md`
|
||||
- `OpenTelemetryPlan/05-configuration-reference.md`
|
||||
- `docker/telemetry/TESTING.md`
|
||||
|
||||
---
|
||||
|
||||
## Task 7.9: ValidationTracker — Validation Agreement Computation
|
||||
|
||||
> **Source**: [External Dashboard Parity](../docs/superpowers/specs/2026-03-30-external-dashboard-parity-design.md) — the most valuable metric from the community [xrpl-validator-dashboard](https://github.com/realgrapedrop/xrpl-validator-dashboard).
|
||||
>
|
||||
> **Upstream**: Phase 4 Task 4.8 (validation span attributes provide ledger hash context).
|
||||
> **Downstream**: Phase 9 (Validator Health dashboard), Phase 10 (validation checks), Phase 11 (agreement alert rules).
|
||||
|
||||
**Objective**: Implement a stateful class that tracks whether our validator's validations agree with network consensus, maintaining rolling 1h and 24h windows with an 8-second grace period and 5-minute late repair window.
|
||||
|
||||
**Architecture**:
|
||||
|
||||
```
|
||||
consensus.validation.send ────> ValidationTracker ────> MetricsRegistry
|
||||
(records our validation (reconciles after (exports agreement
|
||||
for ledger X) 8s grace period) gauges every 10s)
|
||||
|
||||
ledger.validate ──────────────> ValidationTracker
|
||||
(records which ledger (marks ledger X as
|
||||
network validated) agreed or missed)
|
||||
```
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Create `src/xrpld/telemetry/ValidationTracker.h`:
|
||||
- `recordOurValidation(ledgerHash, ledgerSeq)` — called when we send a validation
|
||||
- `recordNetworkValidation(ledgerHash, seq)` — called when a ledger is fully validated
|
||||
- `reconcile()` — called periodically; reconciles pending ledger events after 8s grace period
|
||||
- Getters: `agreementPct1h()`, `agreementPct24h()`, `agreements1h()`, `missed1h()`, `agreements24h()`, `missed24h()`, `totalAgreements()`, `totalMissed()`, `totalValidationsSent()`, `totalValidationsChecked()`
|
||||
- Thread-safety: atomics for counters, mutex for window deques
|
||||
|
||||
- Create `src/xrpld/telemetry/detail/ValidationTracker.cpp`:
|
||||
- Reconciliation logic: after 8s grace period, check if `weValidated && networkValidated && sameHash` → agreement; else missed
|
||||
- Late repair: if a late validation arrives within 5 minutes, correct a false-positive miss
|
||||
- Sliding window: `std::deque<WindowEvent>` evicts entries older than 1h/24h on each reconciliation pass
|
||||
- Ring buffer of 1000 `LedgerEvent` structs for pending reconciliation
|
||||
|
||||
- Add recording hooks (modifying Phase 4 code from Phase 7 branch):
|
||||
- `RCLConsensus.cpp` `validate()`: call `tracker.recordOurValidation()`
|
||||
- `LedgerMaster.cpp` fully-validated path: call `tracker.recordNetworkValidation()`
|
||||
|
||||
**Key data structures**:
|
||||
|
||||
```cpp
|
||||
struct LedgerEvent {
|
||||
uint256 ledgerHash;
|
||||
LedgerIndex seq;
|
||||
TimePoint closeTime;
|
||||
bool weValidated = false;
|
||||
bool networkValidated = false;
|
||||
bool reconciled = false;
|
||||
bool agreed = false;
|
||||
};
|
||||
|
||||
struct WindowEvent {
|
||||
TimePoint time;
|
||||
bool agreed;
|
||||
};
|
||||
```
|
||||
|
||||
**Key new files**:
|
||||
|
||||
- `src/xrpld/telemetry/ValidationTracker.h`
|
||||
- `src/xrpld/telemetry/detail/ValidationTracker.cpp`
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/telemetry/MetricsRegistry.h` (add ValidationTracker member)
|
||||
- `src/xrpld/telemetry/MetricsRegistry.cpp` (add gauge callback reading from tracker)
|
||||
- `src/xrpld/app/consensus/RCLConsensus.cpp` (add recording hooks)
|
||||
- `src/xrpld/app/ledger/detail/LedgerMaster.cpp` (add recording hook)
|
||||
|
||||
**Exit Criteria**:
|
||||
|
||||
- [ ] ValidationTracker correctly tracks agreement with 8s grace period
|
||||
- [ ] 5-minute late repair corrects false-positive misses
|
||||
- [ ] Thread-safe (atomics + mutex for window deques)
|
||||
- [ ] Rolling windows correctly evict stale entries
|
||||
- [ ] Unit tests: normal agreement, missed validation, late repair, window eviction
|
||||
|
||||
---
|
||||
|
||||
## Task 7.10: Validator Health Observable Gauges
|
||||
|
||||
> **Source**: [External Dashboard Parity](../docs/superpowers/specs/2026-03-30-external-dashboard-parity-design.md)
|
||||
|
||||
**Objective**: Export amendment blocked, UNL health, and quorum data as a native OTel observable gauge.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- In `MetricsRegistry.cpp` `registerAsyncGauges()`, add:
|
||||
|
||||
```cpp
|
||||
validatorHealthGauge_ = meter_->CreateDoubleObservableGauge(
|
||||
"xrpld_validator_health", "Validator health indicators");
|
||||
```
|
||||
|
||||
**Gauge label values**:
|
||||
|
||||
| Label `metric=` | Type | Source |
|
||||
| ------------------- | ------ | ------------------------------------------------- |
|
||||
| `amendment_blocked` | int64 | `app_.getOPs().isAmendmentBlocked()` → 0/1 |
|
||||
| `unl_blocked` | int64 | `app_.getOPs().isUNLBlocked()` → 0/1 |
|
||||
| `unl_expiry_days` | double | `app_.validators().expires()` → days until expiry |
|
||||
| `validation_quorum` | int64 | `app_.validators().quorum()` |
|
||||
|
||||
### Sub-task 7.10a: Per-Validator Validation Count (Flag Ledger Window)
|
||||
|
||||
**Objective**: Track how many ledgers each UNL validator has validated over
|
||||
the last 256 consecutive ledgers (one flag ledger window). This is the key
|
||||
UNL participation metric — validators consistently below threshold may be
|
||||
candidates for removal from the UNL.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Add a new observable gauge:
|
||||
|
||||
```cpp
|
||||
validatorParticipationGauge_ = meter_->CreateInt64ObservableGauge(
|
||||
"xrpld_validator_participation",
|
||||
"Per-validator validation count over the last 256 ledgers");
|
||||
```
|
||||
|
||||
- The callback queries `app_.getValidations()` to get the trusted
|
||||
validation set for each of the last 256 ledger hashes (from
|
||||
`LedgerMaster::getValidatedLedger()` walking backwards). For each
|
||||
validator public key in the UNL, count how many of those 256 ledgers
|
||||
have a matching validation.
|
||||
|
||||
- **Label dimensions**:
|
||||
- `validator` — base58-encoded validator master public key
|
||||
- `exported_instance` — this node's identity (standard)
|
||||
|
||||
- **Emission**: every flag ledger (256 ledgers, ~15 minutes) or on a
|
||||
10-second async gauge callback with cached results (recompute only
|
||||
at flag ledger boundaries).
|
||||
|
||||
- **Data source**: `RCLValidations::getTrustedForLedger(hash, seq)` returns
|
||||
`std::vector<std::shared_ptr<STValidation>>` with `getSignerPublic()`
|
||||
for each. The UNL list is from `app_.getValidators().getTrustedMasterKeys()`.
|
||||
|
||||
- **Dashboard panel**: Add a table panel to the Validator Health dashboard
|
||||
showing `xrpld_validator_participation` grouped by `validator` label,
|
||||
with a threshold color (green >= 240, yellow >= 200, red < 200).
|
||||
|
||||
**Key modified files**: `src/xrpld/telemetry/MetricsRegistry.h/.cpp`
|
||||
|
||||
**Exit Criteria**:
|
||||
|
||||
- [ ] Gauge emits one time series per UNL validator
|
||||
- [ ] Values range 0-256 and update at flag ledger boundaries
|
||||
- [ ] Grafana table panel shows per-validator participation
|
||||
- [ ] Validators below 75% participation are highlighted in red
|
||||
|
||||
---
|
||||
|
||||
**Key modified files**: `src/xrpld/telemetry/MetricsRegistry.h/.cpp`
|
||||
|
||||
**Exit Criteria**:
|
||||
|
||||
- [ ] All 4 base label values emitted every 10s
|
||||
- [ ] `unl_expiry_days` is negative when expired, positive when active
|
||||
- [ ] Per-validator participation gauge emits at flag ledger boundaries
|
||||
- [ ] Values visible in Prometheus
|
||||
|
||||
---
|
||||
|
||||
## Task 7.11: Peer Quality Observable Gauges
|
||||
|
||||
> **Source**: [External Dashboard Parity](../docs/superpowers/specs/2026-03-30-external-dashboard-parity-design.md)
|
||||
|
||||
**Objective**: Export peer health aggregates (latency P90, insane peers, version awareness) as a native OTel observable gauge.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- In `MetricsRegistry.cpp` `registerAsyncGauges()`, add a callback that iterates `app_.overlay().foreach(...)` to:
|
||||
- Collect per-peer latency values, sort, compute P90
|
||||
- Count peers with `tracking_ == diverged` (insane)
|
||||
- Compare peer `getVersion()` to own version for upgrade awareness
|
||||
|
||||
**Gauge label values**:
|
||||
|
||||
| Label `metric=` | Type | Source |
|
||||
| -------------------------- | ------ | ------------------------------------- |
|
||||
| `peer_latency_p90_ms` | double | P90 from sorted peer latencies |
|
||||
| `peers_insane_count` | int64 | Peers with diverged tracking status |
|
||||
| `peers_higher_version_pct` | double | % of peers on newer xrpld version |
|
||||
| `upgrade_recommended` | int64 | 1 if `peers_higher_version_pct > 60%` |
|
||||
|
||||
**Implementation note**: The callback runs every 10s on the metrics reader thread. Iterating ~50-200 peers is acceptable overhead.
|
||||
|
||||
**Key modified files**: `src/xrpld/telemetry/MetricsRegistry.h/.cpp`
|
||||
|
||||
**Exit Criteria**:
|
||||
|
||||
- [ ] P90 latency computed correctly
|
||||
- [ ] Insane count matches `peers` RPC output
|
||||
- [ ] Version comparison handles format variations (e.g., "xrpld-2.4.0-rc1")
|
||||
|
||||
---
|
||||
|
||||
## Task 7.12: Ledger Economy Observable Gauges
|
||||
|
||||
> **Source**: [External Dashboard Parity](../docs/superpowers/specs/2026-03-30-external-dashboard-parity-design.md)
|
||||
|
||||
**Objective**: Export fee, reserve, ledger age, and transaction rate as a native OTel observable gauge.
|
||||
|
||||
**Gauge label values**:
|
||||
|
||||
| Label `metric=` | Type | Source |
|
||||
| -------------------- | ------ | --------------------------------------------------- |
|
||||
| `base_fee_xrp` | double | Base fee from validated ledger fee settings (drops) |
|
||||
| `reserve_base_xrp` | double | Account reserve from validated ledger (drops) |
|
||||
| `reserve_inc_xrp` | double | Owner reserve increment (drops) |
|
||||
| `ledger_age_seconds` | double | `now - lastValidatedCloseTime` |
|
||||
| `transaction_rate` | double | Derived: tx count delta / time delta (smoothed) |
|
||||
|
||||
**Key modified files**: `src/xrpld/telemetry/MetricsRegistry.h/.cpp`
|
||||
|
||||
**Exit Criteria**:
|
||||
|
||||
- [ ] Fee values match `server_info` RPC output
|
||||
- [ ] `ledger_age_seconds` increases monotonically between ledger closes
|
||||
- [ ] `transaction_rate` is smoothed (rolling average)
|
||||
|
||||
---
|
||||
|
||||
## Task 7.13: State Tracking Observable Gauges
|
||||
|
||||
> **Source**: [External Dashboard Parity](../docs/superpowers/specs/2026-03-30-external-dashboard-parity-design.md)
|
||||
|
||||
**Objective**: Export extended state value (0-6 encoding combining OperatingMode + ConsensusMode) and time-in-current-state.
|
||||
|
||||
**Gauge label values**:
|
||||
|
||||
| Label `metric=` | Type | Source |
|
||||
| ------------------------------- | ------ | ----------------------------------------------- |
|
||||
| `state_value` | int64 | 0-6 encoding (see spec for mapping) |
|
||||
| `time_in_current_state_seconds` | double | `now - lastModeChangeTime` from StateAccounting |
|
||||
|
||||
**State value encoding**: 0=disconnected, 1=connected, 2=syncing, 3=tracking, 4=full, 5=validating (full + validating), 6=proposing (full + proposing).
|
||||
|
||||
**Key modified files**: `src/xrpld/telemetry/MetricsRegistry.h/.cpp`
|
||||
|
||||
**Exit Criteria**:
|
||||
|
||||
- [ ] `state_value` correctly combines OperatingMode and ConsensusMode
|
||||
- [ ] `time_in_current_state_seconds` resets on mode change
|
||||
|
||||
---
|
||||
|
||||
## Task 7.14: Storage Detail and Sync Info Gauges
|
||||
|
||||
> **Source**: [External Dashboard Parity](../docs/superpowers/specs/2026-03-30-external-dashboard-parity-design.md)
|
||||
|
||||
**Objective**: Export NuDB-specific storage size and initial sync duration.
|
||||
|
||||
**Gauge label values**:
|
||||
|
||||
| Gauge Name | Label `metric=` | Type | Source |
|
||||
| ---------------------- | ------------------------------- | ------ | ----------------------------- |
|
||||
| `xrpld_storage_detail` | `nudb_bytes` | int64 | NuDB backend file size |
|
||||
| `xrpld_sync_info` | `initial_sync_duration_seconds` | double | Time from start to first FULL |
|
||||
|
||||
**Key modified files**: `src/xrpld/telemetry/MetricsRegistry.h/.cpp`
|
||||
|
||||
**Exit Criteria**:
|
||||
|
||||
- [ ] NuDB file size reported in bytes (0 if NuDB not configured)
|
||||
- [ ] Sync duration captured once and remains stable after reaching FULL
|
||||
|
||||
---
|
||||
|
||||
## Task 7.15: New Synchronous Counters
|
||||
|
||||
> **Source**: [External Dashboard Parity](../docs/superpowers/specs/2026-03-30-external-dashboard-parity-design.md)
|
||||
|
||||
**Objective**: Add 7 new event counters incremented at their respective instrumentation sites.
|
||||
|
||||
| Counter Name | Increment Site | Source File |
|
||||
| ----------------------------------- | -------------------------------- | --------------------- |
|
||||
| `xrpld_ledgers_closed_total` | `onAccept()` in consensus | RCLConsensus.cpp |
|
||||
| `xrpld_validations_sent_total` | `validate()` in consensus | RCLConsensus.cpp |
|
||||
| `xrpld_validations_checked_total` | Network validation received | LedgerMaster.cpp |
|
||||
| `xrpld_validation_agreements_total` | ValidationTracker reconciliation | ValidationTracker.cpp |
|
||||
| `xrpld_validation_missed_total` | ValidationTracker reconciliation | ValidationTracker.cpp |
|
||||
| `xrpld_state_changes_total` | `setMode()` in NetworkOPs | NetworkOPs.cpp |
|
||||
| `xrpld_jq_trans_overflow_total` | Job queue overflow path | JobQueue.cpp |
|
||||
|
||||
**Key modified files**: `src/xrpld/telemetry/MetricsRegistry.h/.cpp` (declarations), plus recording sites in RCLConsensus.cpp, LedgerMaster.cpp, NetworkOPs.cpp, JobQueue.cpp
|
||||
|
||||
**Exit Criteria**:
|
||||
|
||||
- [ ] All 7 counters monotonically increase during normal operation
|
||||
- [ ] Counter values match expected rates (e.g., ledgers_closed ≈ 1 per 3-5s)
|
||||
|
||||
---
|
||||
|
||||
## Task 7.16: Validation Agreement Observable Gauge
|
||||
|
||||
> **Source**: [External Dashboard Parity](../docs/superpowers/specs/2026-03-30-external-dashboard-parity-design.md)
|
||||
|
||||
**Objective**: Export rolling window agreement stats from `ValidationTracker` (Task 7.9).
|
||||
|
||||
**Gauge label values**:
|
||||
|
||||
| Gauge Name | Label `metric=` | Type | Source |
|
||||
| ---------------------------- | ------------------- | ------ | --------------------------- |
|
||||
| `xrpld_validation_agreement` | `agreement_pct_1h` | double | `tracker.agreementPct1h()` |
|
||||
| | `agreements_1h` | int64 | `tracker.agreements1h()` |
|
||||
| | `missed_1h` | int64 | `tracker.missed1h()` |
|
||||
| | `agreement_pct_24h` | double | `tracker.agreementPct24h()` |
|
||||
| | `agreements_24h` | int64 | `tracker.agreements24h()` |
|
||||
| | `missed_24h` | int64 | `tracker.missed24h()` |
|
||||
|
||||
**Key modified files**: `src/xrpld/telemetry/MetricsRegistry.cpp`
|
||||
|
||||
**Exit Criteria**:
|
||||
|
||||
- [ ] Agreement percentages in range [0.0, 100.0]
|
||||
- [ ] Window stats stabilize after 1h/24h of operation
|
||||
|
||||
---
|
||||
|
||||
## Summary Table
|
||||
|
||||
| Task | Description | New Files | Modified Files | Depends On |
|
||||
| ---- | -------------------------------------- | --------- | -------------- | ---------- |
|
||||
| 7.1 | Add OTel Metrics SDK to build deps | 0 | 2 | — |
|
||||
| 7.2 | Implement OTelCollector class | 2 | 0 | 7.1 |
|
||||
| 7.3 | Update CollectorManager config routing | 0 | 2 | 7.2 |
|
||||
| 7.4 | Update OTel Collector YAML and Docker | 0 | 2 | 7.3 |
|
||||
| 7.5 | Preserve metric names in Prometheus | 0 | 1 | 7.2 |
|
||||
| 7.6 | Update Grafana dashboards (if needed) | 0 | 3 | 7.5 |
|
||||
| 7.7 | Update integration tests | 0 | 1 | 7.4 |
|
||||
| 7.8 | Update documentation | 0 | 4 | 7.6 |
|
||||
| 7.9 | ValidationTracker (agreement tracking) | 2 | 4 | 7.2, P4.8 |
|
||||
| 7.10 | Validator health observable gauges | 0 | 2 | 7.2 |
|
||||
| 7.11 | Peer quality observable gauges | 0 | 2 | 7.2 |
|
||||
| 7.12 | Ledger economy observable gauges | 0 | 2 | 7.2 |
|
||||
| 7.13 | State tracking observable gauges | 0 | 2 | 7.2 |
|
||||
| 7.14 | Storage detail and sync info gauges | 0 | 2 | 7.2 |
|
||||
| 7.15 | New synchronous counters | 0 | 6 | 7.2 |
|
||||
| 7.16 | Validation agreement observable gauge | 0 | 1 | 7.9 |
|
||||
|
||||
**Parallel work**: Tasks 7.4 and 7.5 can run in parallel after 7.2/7.3 complete. Task 7.6 depends on 7.5's findings. Tasks 7.7 and 7.8 can run in parallel after 7.6. Tasks 7.10-7.14 can all run in parallel after 7.2. Task 7.15 depends on 7.2. Task 7.16 depends on 7.9. Task 7.9 depends on 7.2 and Phase 4 Task 4.8.
|
||||
|
||||
**Exit Criteria** (from [06-implementation-phases.md §6.8](./06-implementation-phases.md)):
|
||||
|
||||
- [ ] All 255+ metrics visible in Prometheus via OTLP pipeline (no StatsD receiver)
|
||||
- [ ] `server=otel` is the default in development docker-compose
|
||||
- [ ] `server=statsd` still works as a fallback
|
||||
- [ ] Existing Grafana dashboards display data correctly
|
||||
- [ ] Integration test passes with OTLP-only metrics pipeline
|
||||
- [ ] No performance regression vs StatsD baseline (< 1% CPU overhead)
|
||||
- [ ] Deferred Task 6.1 (`|m` wire format) no longer relevant — Meter mapped to OTel Counter
|
||||
- [ ] ValidationTracker agreement % stabilizes after 1h under normal consensus
|
||||
- [ ] All new gauges and counters visible in Prometheus with non-zero values
|
||||
241
OpenTelemetryPlan/Phase8_taskList.md
Normal file
241
OpenTelemetryPlan/Phase8_taskList.md
Normal file
@@ -0,0 +1,241 @@
|
||||
# Phase 8: Log-Trace Correlation and Centralized Log Ingestion — Task List
|
||||
|
||||
> **Goal**: Inject trace context (trace_id, span_id) into xrpld's Journal log output for log-trace correlation, and add OTel Collector filelog receiver to ingest logs into Grafana Loki for unified observability.
|
||||
>
|
||||
> **Scope**: Two independent sub-phases — 8a (code change: trace_id in logs) and 8b (infra only: filelog receiver to Loki). No changes to the `beast::Journal` public API.
|
||||
>
|
||||
> **Branch**: `pratik/otel-phase8-log-correlation` (from `pratik/otel-phase7-native-metrics`)
|
||||
|
||||
### Related Plan Documents
|
||||
|
||||
| Document | Relevance |
|
||||
| ---------------------------------------------------------------- | -------------------------------------------------------------- |
|
||||
| [06-implementation-phases.md](./06-implementation-phases.md) | Phase 8 plan: motivation, architecture, exit criteria (§6.8.1) |
|
||||
| [07-observability-backends.md](./07-observability-backends.md) | Loki backend recommendation, Grafana data source provisioning |
|
||||
| [Phase7_taskList.md](./Phase7_taskList.md) | Prerequisite — native OTel metrics pipeline must be working |
|
||||
| [05-configuration-reference.md](./05-configuration-reference.md) | `[telemetry]` config (trace_id injection toggle) |
|
||||
|
||||
---
|
||||
|
||||
## Task 8.1: Inject trace_id into Logs::format()
|
||||
|
||||
**Objective**: Add OTel trace context to every log line that is emitted within an active span.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Edit `src/libxrpl/basics/Log.cpp`:
|
||||
- In `Logs::format()` (around line 346), after severity is appended, check for active OTel span. The implementation checks the context value directly to avoid the heap allocation that `GetSpan()` performs on the no-span path:
|
||||
```cpp
|
||||
#ifdef XRPL_ENABLE_TELEMETRY
|
||||
{
|
||||
auto context = opentelemetry::context::RuntimeContext::GetCurrent();
|
||||
auto spanValue = context.GetValue(opentelemetry::trace::kSpanKey);
|
||||
if (opentelemetry::nostd::holds_alternative<
|
||||
opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>>(spanValue))
|
||||
{
|
||||
auto span = opentelemetry::nostd::get<
|
||||
opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>>(spanValue);
|
||||
auto spanCtx = span->GetContext();
|
||||
if (spanCtx.IsValid())
|
||||
{
|
||||
char traceId[32], spanId[16];
|
||||
spanCtx.trace_id().ToLowerBase16(
|
||||
opentelemetry::nostd::span<char, 32>{traceId});
|
||||
spanCtx.span_id().ToLowerBase16(
|
||||
opentelemetry::nostd::span<char, 16>{spanId});
|
||||
output += "trace_id=";
|
||||
output.append(traceId, 32);
|
||||
output += " span_id=";
|
||||
output.append(spanId, 16);
|
||||
output += ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
```
|
||||
- Add `#include` for OTel context headers, guarded by `#ifdef XRPL_ENABLE_TELEMETRY`
|
||||
|
||||
- Edit `include/xrpl/basics/Log.h`:
|
||||
- No changes needed — format() signature unchanged
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/libxrpl/basics/Log.cpp`
|
||||
|
||||
**Performance note**: The implementation checks the thread-local context value directly (avoiding the heap allocation that `GetSpan()` performs on the no-span path). On threads without an active span (~99% of log lines), the cost is a thread-local read + variant type check (~15-20ns). On the active-span path, an additional shared_ptr copy + `GetContext()` + `IsValid()` adds ~50ns total. Overhead is negligible at typical logging rates.
|
||||
|
||||
---
|
||||
|
||||
## Task 8.2: Add Loki to Docker Compose Stack
|
||||
|
||||
**Objective**: Add Grafana Loki as a log storage backend in the development observability stack.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Edit `docker/telemetry/docker-compose.yml`:
|
||||
- Add Loki service:
|
||||
```yaml
|
||||
loki:
|
||||
image: grafana/loki:2.9.0
|
||||
ports:
|
||||
- "3100:3100"
|
||||
command: -config.file=/etc/loki/local-config.yaml
|
||||
```
|
||||
- Add Loki as a Grafana data source in provisioning
|
||||
|
||||
- Create `docker/telemetry/grafana/provisioning/datasources/loki.yaml`:
|
||||
- Configure Loki data source with derived fields linking `trace_id` to Tempo
|
||||
|
||||
**Key new files**:
|
||||
|
||||
- `docker/telemetry/grafana/provisioning/datasources/loki.yaml`
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `docker/telemetry/docker-compose.yml`
|
||||
|
||||
---
|
||||
|
||||
## Task 8.3: Add Filelog Receiver to OTel Collector
|
||||
|
||||
**Objective**: Configure the OTel Collector to tail xrpld's log file and export to Loki.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Edit `docker/telemetry/otel-collector-config.yaml`:
|
||||
- Add `filelog` receiver:
|
||||
```yaml
|
||||
receivers:
|
||||
filelog:
|
||||
include: [/var/log/rippled/debug.log]
|
||||
operators:
|
||||
- type: regex_parser
|
||||
regex: '^(?P<timestamp>\S+)\s+(?P<partition>\S+):(?P<severity>\S+)\s+(?:trace_id=(?P<trace_id>[a-f0-9]+)\s+span_id=(?P<span_id>[a-f0-9]+)\s+)?(?P<message>.*)$'
|
||||
timestamp:
|
||||
parse_from: attributes.timestamp
|
||||
layout: "%Y-%m-%dT%H:%M:%S.%fZ"
|
||||
```
|
||||
- Add logs pipeline:
|
||||
```yaml
|
||||
service:
|
||||
pipelines:
|
||||
logs:
|
||||
receivers: [filelog]
|
||||
processors: [batch]
|
||||
exporters: [otlp/loki]
|
||||
```
|
||||
- Add Loki exporter:
|
||||
```yaml
|
||||
exporters:
|
||||
otlp/loki:
|
||||
endpoint: loki:3100
|
||||
tls:
|
||||
insecure: true
|
||||
```
|
||||
|
||||
- Mount xrpld's log directory into the collector container via docker-compose volume
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `docker/telemetry/otel-collector-config.yaml`
|
||||
- `docker/telemetry/docker-compose.yml`
|
||||
|
||||
---
|
||||
|
||||
## Task 8.4: Configure Grafana Trace-to-Log Correlation
|
||||
|
||||
**Objective**: Enable one-click navigation from Tempo traces to Loki logs in Grafana.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Edit Grafana Tempo data source provisioning to add `tracesToLogs` configuration:
|
||||
|
||||
```yaml
|
||||
tracesToLogs:
|
||||
datasourceUid: loki
|
||||
filterByTraceID: true
|
||||
filterBySpanID: false
|
||||
tags: ["partition", "severity"]
|
||||
```
|
||||
|
||||
- Edit Grafana Loki data source provisioning to add `derivedFields` linking trace_id back to Tempo:
|
||||
```yaml
|
||||
derivedFields:
|
||||
- datasourceUid: tempo
|
||||
matcherRegex: "trace_id=(\\w+)"
|
||||
name: TraceID
|
||||
url: "$${__value.raw}"
|
||||
```
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `docker/telemetry/grafana/provisioning/datasources/loki.yaml`
|
||||
- `docker/telemetry/grafana/provisioning/datasources/` (Tempo data source file)
|
||||
|
||||
---
|
||||
|
||||
## Task 8.5: Update Integration Tests
|
||||
|
||||
**Objective**: Verify trace_id appears in logs and Loki correlation works.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Edit `docker/telemetry/integration-test.sh`:
|
||||
- After sending RPC requests (which create spans), grep xrpld's log output for `trace_id=`
|
||||
- Verify trace_id matches a trace visible in Tempo
|
||||
- Optionally: query Loki via API to confirm log ingestion
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `docker/telemetry/integration-test.sh`
|
||||
|
||||
---
|
||||
|
||||
## Task 8.6: Update Documentation
|
||||
|
||||
**Objective**: Document the log correlation feature in runbook and reference docs.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Edit `docs/telemetry-runbook.md`:
|
||||
- Add "Log-Trace Correlation" section explaining how to use Grafana Tempo -> Loki linking
|
||||
- Add LogQL query examples for filtering by trace_id
|
||||
|
||||
- Edit `OpenTelemetryPlan/09-data-collection-reference.md`:
|
||||
- Add new section "3. Log Correlation" between SpanMetrics and StatsD sections
|
||||
- Document the log format with trace_id injection
|
||||
- Document Loki as a new backend
|
||||
|
||||
- Edit `docker/telemetry/TESTING.md`:
|
||||
- Add log correlation verification steps
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `docs/telemetry-runbook.md`
|
||||
- `OpenTelemetryPlan/09-data-collection-reference.md`
|
||||
- `docker/telemetry/TESTING.md`
|
||||
|
||||
---
|
||||
|
||||
## Summary Table
|
||||
|
||||
| Task | Description | Sub-Phase | New Files | Modified Files | Depends On |
|
||||
| ---- | ------------------------------------------ | --------- | --------- | -------------- | ---------- |
|
||||
| 8.1 | Inject trace_id into Logs::format() | 8a | 0 | 1 | Phase 7 |
|
||||
| 8.2 | Add Loki to Docker Compose stack | 8b | 1 | 1 | -- |
|
||||
| 8.3 | Add filelog receiver to OTel Collector | 8b | 0 | 2 | 8.1, 8.2 |
|
||||
| 8.4 | Configure Grafana trace-to-log correlation | 8b | 0 | 2 | 8.3 |
|
||||
| 8.5 | Update integration tests | 8a + 8b | 0 | 1 | 8.4 |
|
||||
| 8.6 | Update documentation | 8a + 8b | 0 | 3 | 8.5 |
|
||||
|
||||
**Parallel work**: Task 8.2 (Loki infra) can run in parallel with Task 8.1 (code change). Tasks 8.3-8.6 are sequential.
|
||||
|
||||
**Exit Criteria** (from [06-implementation-phases.md §6.8.1](./06-implementation-phases.md)):
|
||||
|
||||
- [ ] Log lines within active spans contain `trace_id=<hex> span_id=<hex>`
|
||||
- [ ] Log lines outside spans have no trace context (no empty fields)
|
||||
- [ ] Loki ingests xrpld logs via OTel Collector filelog receiver
|
||||
- [ ] Grafana Tempo -> Loki one-click correlation works
|
||||
- [ ] Grafana Loki -> Tempo reverse lookup works via derived field
|
||||
- [ ] Integration test verifies trace_id presence in logs
|
||||
- [ ] No performance regression from trace_id injection (< 0.1% overhead)
|
||||
447
OpenTelemetryPlan/Phase9_taskList.md
Normal file
447
OpenTelemetryPlan/Phase9_taskList.md
Normal file
@@ -0,0 +1,447 @@
|
||||
# Phase 9: Internal Metric Instrumentation Gap Fill — Task List
|
||||
|
||||
> **Status**: Future Enhancement
|
||||
>
|
||||
> **Goal**: Instrument xrpld to emit ~50+ metrics that exist in `get_counts`/`server_info`/TxQ/PerfLog but currently lack time-series export via the OTel or beast::insight pipelines.
|
||||
>
|
||||
> **Scope**: Hybrid approach — extend `beast::insight` for metrics near existing registrations, use OTel Metrics SDK `ObservableGauge` callbacks for new categories (TxQ, PerfLog, CountedObjects).
|
||||
>
|
||||
> **Branch**: `pratik/otel-phase9-metric-gap-fill` (from `pratik/otel-phase8-log-correlation`)
|
||||
>
|
||||
> **Depends on**: Phase 7 (native OTel metrics pipeline) and Phase 8 (log-trace correlation)
|
||||
|
||||
### Related Plan Documents
|
||||
|
||||
| Document | Relevance |
|
||||
| -------------------------------------------------------------------- | -------------------------------------------------------------- |
|
||||
| [06-implementation-phases.md](./06-implementation-phases.md) | Phase 9 plan: motivation, architecture, exit criteria (§6.8.2) |
|
||||
| [09-data-collection-reference.md](./09-data-collection-reference.md) | Current metric inventory + future metrics section |
|
||||
| [Phase7_taskList.md](./Phase7_taskList.md) | Prerequisite — OTel Metrics SDK and `OTelCollector` class |
|
||||
| [Phase8_taskList.md](./Phase8_taskList.md) | Prerequisite — log-trace correlation |
|
||||
|
||||
### Third-Party Consumer Context
|
||||
|
||||
These metrics serve multiple external consumer categories identified during research:
|
||||
|
||||
| Consumer Category | Key Metrics They Need |
|
||||
| ------------------------- | --------------------------------------------------------------- |
|
||||
| **Exchanges** | Fee escalation levels, TxQ depth, settlement latency |
|
||||
| **Payment Processors** | Load factors, io_latency, transaction throughput |
|
||||
| **Analytics Providers** | NodeStore I/O, cache hit rates, counted objects |
|
||||
| **Validators/Operators** | Per-job execution times, PerfLog RPC counters, consensus timing |
|
||||
| **Academic Researchers** | Consensus performance time-series, fee market dynamics |
|
||||
| **Institutional Custody** | Server health scores, reserve calculations, node availability |
|
||||
|
||||
---
|
||||
|
||||
## Task 9.1: NodeStore I/O Metrics
|
||||
|
||||
**Objective**: Export node store read/write performance as time-series metrics.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- In `src/libxrpl/nodestore/Database.cpp`, extend existing `beast::insight` registrations to add:
|
||||
- Gauge: `node_reads_total` (cumulative read operations)
|
||||
- Gauge: `node_reads_hit` (cache-served reads)
|
||||
- Gauge: `node_writes` (cumulative write operations)
|
||||
- Gauge: `node_written_bytes` (cumulative bytes written)
|
||||
- Gauge: `node_read_bytes` (cumulative bytes read)
|
||||
- Gauge: `node_reads_duration_us` (cumulative read time in microseconds)
|
||||
- Gauge: `write_load` (current write load score)
|
||||
- Gauge: `read_queue` (items in read queue)
|
||||
|
||||
- These values are already computed in `Database::getCountsJson()` (line ~236). Wire the same counters to `beast::insight` hooks.
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/libxrpl/nodestore/Database.cpp`
|
||||
- `src/libxrpl/nodestore/Database.h` (add insight members)
|
||||
|
||||
**Derived Prometheus metrics**: `xrpld_nodestore_reads_total`, `xrpld_nodestore_reads_hit`, `xrpld_nodestore_write_load`, etc.
|
||||
|
||||
**Grafana dashboard**: Add "NodeStore I/O" panel group to _Node Health_ dashboard.
|
||||
|
||||
---
|
||||
|
||||
## Task 9.2: Cache Hit Rate Metrics
|
||||
|
||||
**Objective**: Export SHAMap and ledger cache performance as time-series gauges.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Register OTel `ObservableGauge` callbacks (via Phase 7's `OTelCollector`) for:
|
||||
- `SLE_hit_rate` — SLE cache hit rate (0.0–1.0)
|
||||
- `ledger_hit_rate` — Ledger object cache hit rate
|
||||
- `AL_hit_rate` — AcceptedLedger cache hit rate
|
||||
- `treenode_cache_size` — SHAMap TreeNode cache size (entries)
|
||||
- `treenode_track_size` — Tracked tree nodes
|
||||
- `fullbelow_size` — FullBelow cache size
|
||||
|
||||
- The callback should read from the same sources as `GetCounts.cpp` handler (line ~43).
|
||||
|
||||
- Create a centralized `MetricsRegistry` class that holds all OTel async gauge registrations, polled at 10-second intervals by the `PeriodicMetricReader`.
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- New: `src/xrpld/telemetry/MetricsRegistry.h` / `.cpp`
|
||||
- `src/xrpld/rpc/handlers/GetCounts.cpp` (extract shared access methods)
|
||||
- `src/xrpld/app/main/Application.cpp` (register MetricsRegistry at startup)
|
||||
|
||||
**Derived Prometheus metrics**: `xrpld_cache_SLE_hit_rate`, `xrpld_cache_ledger_hit_rate`, `xrpld_cache_treenode_size`, etc.
|
||||
|
||||
---
|
||||
|
||||
## Task 9.3: Transaction Queue (TxQ) Metrics
|
||||
|
||||
**Objective**: Export TxQ depth, capacity, and fee escalation levels as time-series.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Register OTel `ObservableGauge` callbacks for TxQ state (from `TxQ.h` line ~143):
|
||||
- `txq_count` — Current transactions in queue
|
||||
- `txq_max_size` — Maximum queue capacity
|
||||
- `txq_in_ledger` — Transactions in current open ledger
|
||||
- `txq_per_ledger` — Expected transactions per ledger
|
||||
- `txq_reference_fee_level` — Reference fee level
|
||||
- `txq_min_processing_fee_level` — Minimum fee to get processed
|
||||
- `txq_med_fee_level` — Median fee level in queue
|
||||
- `txq_open_ledger_fee_level` — Open ledger fee escalation level
|
||||
|
||||
- Add to the `MetricsRegistry` (Task 9.2).
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/telemetry/MetricsRegistry.cpp` (add TxQ callbacks)
|
||||
- `src/xrpld/app/tx/detail/TxQ.h` (expose metrics accessor if needed)
|
||||
|
||||
**Derived Prometheus metrics**: `xrpld_txq_count`, `xrpld_txq_max_size`, `xrpld_txq_open_ledger_fee_level`, etc.
|
||||
|
||||
**Grafana dashboard**: New _Fee Market & TxQ_ dashboard (`fee-market`).
|
||||
|
||||
---
|
||||
|
||||
## Task 9.4: PerfLog Per-RPC Method Metrics
|
||||
|
||||
**Objective**: Export per-RPC-method call counts and latency as OTel metrics.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Register OTel instruments for PerfLog RPC counters (from `PerfLogImp.cpp` line ~63):
|
||||
- Counter: `xrpld_rpc_method_started_total{method="<name>"}` — calls started
|
||||
- Counter: `xrpld_rpc_method_finished_total{method="<name>"}` — calls completed
|
||||
- Counter: `xrpld_rpc_method_errored_total{method="<name>"}` — calls errored
|
||||
- Histogram: `xrpld_rpc_method_duration_us{method="<name>"}` — execution time distribution
|
||||
|
||||
- Use OTel `Counter<int64_t>` and `Histogram<double>` instruments with `method` attribute label.
|
||||
|
||||
- Hook into the existing PerfLog callback mechanism rather than adding new instrumentation points.
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/perflog/detail/PerfLogImp.cpp` (add OTel instrument updates alongside existing JSON counters)
|
||||
- `src/xrpld/telemetry/MetricsRegistry.cpp` (register instruments)
|
||||
|
||||
**Derived Prometheus metrics**: `xrpld_rpc_method_started_total{method="server_info"}`, `xrpld_rpc_method_duration_us_bucket{method="ledger"}`, etc.
|
||||
|
||||
**Grafana dashboard**: Add "Per-Method RPC Breakdown" panel group to _RPC Performance_ dashboard.
|
||||
|
||||
---
|
||||
|
||||
## Task 9.5: PerfLog Per-Job-Type Metrics
|
||||
|
||||
**Objective**: Export per-job-type queue and execution metrics.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Register OTel instruments for PerfLog job counters:
|
||||
- Counter: `xrpld_job_queued_total{job_type="<name>"}` — jobs queued
|
||||
- Counter: `xrpld_job_started_total{job_type="<name>"}` — jobs started
|
||||
- Counter: `xrpld_job_finished_total{job_type="<name>"}` — jobs completed
|
||||
- Histogram: `xrpld_job_queued_duration_us{job_type="<name>"}` — time spent waiting in queue
|
||||
- Histogram: `xrpld_job_running_duration_us{job_type="<name>"}` — execution time distribution
|
||||
|
||||
- Hook into PerfLog's existing job tracking alongside Task 9.4.
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/perflog/detail/PerfLogImp.cpp`
|
||||
- `src/xrpld/telemetry/MetricsRegistry.cpp`
|
||||
|
||||
**Derived Prometheus metrics**: `xrpld_job_queued_total{job_type="ledgerData"}`, `xrpld_job_running_duration_us_bucket{job_type="transaction"}`, etc.
|
||||
|
||||
**Grafana dashboard**: New _Job Queue Analysis_ dashboard (`job-queue`).
|
||||
|
||||
---
|
||||
|
||||
## Task 9.6: Counted Object Instance Metrics
|
||||
|
||||
**Objective**: Export live instance counts for key internal object types.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Register OTel `ObservableGauge` callbacks for `CountedObject<T>` instance counts:
|
||||
- `xrpld_object_count{type="Transaction"}` — live Transaction objects
|
||||
- `xrpld_object_count{type="Ledger"}` — live Ledger objects
|
||||
- `xrpld_object_count{type="NodeObject"}` — live NodeObject instances
|
||||
- `xrpld_object_count{type="STTx"}` — serialized transaction objects
|
||||
- `xrpld_object_count{type="STLedgerEntry"}` — serialized ledger entries
|
||||
- `xrpld_object_count{type="InboundLedger"}` — ledgers being fetched
|
||||
- `xrpld_object_count{type="Pathfinder"}` — active pathfinding computations
|
||||
- `xrpld_object_count{type="PathRequest"}` — active path requests
|
||||
- `xrpld_object_count{type="HashRouterEntry"}` — hash router entries
|
||||
|
||||
- The `CountedObject` template already tracks these via atomic counters. The callback just reads the current counts.
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/telemetry/MetricsRegistry.cpp` (add counted object callbacks)
|
||||
- `include/xrpl/basics/CountedObject.h` (may need static accessor for iteration)
|
||||
|
||||
**Derived Prometheus metrics**: `xrpld_object_count{type="Transaction"}`, `xrpld_object_count{type="NodeObject"}`, etc.
|
||||
|
||||
**Grafana dashboard**: Add "Object Instance Counts" panel to _Node Health_ dashboard.
|
||||
|
||||
---
|
||||
|
||||
## Task 9.7: Fee Escalation & Load Factor Metrics
|
||||
|
||||
**Objective**: Export the full load factor breakdown as time-series.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Register OTel `ObservableGauge` callbacks for load factors (from `NetworkOPs.cpp` line ~2694):
|
||||
- `load_factor` — combined transaction cost multiplier
|
||||
- `load_factor_server` — server + cluster + network contribution
|
||||
- `load_factor_local` — local server load only
|
||||
- `load_factor_net` — network-wide load estimate
|
||||
- `load_factor_cluster` — cluster peer load
|
||||
- `load_factor_fee_escalation` — open ledger fee escalation
|
||||
- `load_factor_fee_queue` — queue entry fee level
|
||||
|
||||
- These overlap with some existing StatsD metrics but provide finer granularity (individual factor breakdown vs. combined value).
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/telemetry/MetricsRegistry.cpp`
|
||||
- `src/xrpld/app/misc/NetworkOPs.cpp` (expose load factor accessors if needed)
|
||||
|
||||
**Derived Prometheus metrics**: `xrpld_load_factor`, `xrpld_load_factor_fee_escalation`, etc.
|
||||
|
||||
**Grafana dashboard**: Add "Load Factor Breakdown" panel to _Fee Market & TxQ_ dashboard.
|
||||
|
||||
---
|
||||
|
||||
## Task 9.7a: push_metrics.py Parity — Missing Observable Gauges
|
||||
|
||||
**Objective**: Fill the remaining metric gaps between the external `push_metrics.py` script (in `ripplex-ansible`) and the internal OTel `MetricsRegistry` observable gauges. After this task, all metrics collected by `push_metrics.py` that CAN be collected internally are covered.
|
||||
|
||||
**What was done**:
|
||||
|
||||
- Extended existing `cacheHitRateGauge_` callback with `AL_size` (AcceptedLedger cache size)
|
||||
- Extended existing `nodeStoreGauge_` callback with 4 new metrics from `getCountsJson()`:
|
||||
- `node_reads_duration_us` (JSON string — uses `std::stoll(asString())`)
|
||||
- `read_request_bundle` (native JSON int)
|
||||
- `read_threads_running` (native JSON int)
|
||||
- `read_threads_total` (native JSON int)
|
||||
- Added new `xrpld_server_info` Int64ObservableGauge with 8 metrics:
|
||||
- `server_state` — operating mode as int (0=DISCONNECTED .. 4=FULL)
|
||||
- `uptime` — seconds since server start
|
||||
- `peers` — total peer count
|
||||
- `validated_ledger_seq` — validated ledger sequence (atomic read)
|
||||
- `ledger_current_index` — current open ledger sequence
|
||||
- `peer_disconnects_resources` — cumulative resource-related disconnects
|
||||
- `last_close_proposers` — from `getConsensusInfo()["previous_proposers"]`
|
||||
- `last_close_converge_time_ms` — from `getConsensusInfo()["previous_mseconds"]`
|
||||
- Added new `xrpld_build_info` Int64ObservableGauge (info-style, value=1 with `version` label)
|
||||
- Added new `xrpld_complete_ledgers` Int64ObservableGauge parsing comma-separated ranges into `{bound, index}` pairs
|
||||
- Added new `xrpld_db_metrics` Int64ObservableGauge with 4 metrics:
|
||||
- `db_kb_total`, `db_kb_ledger`, `db_kb_transaction` (SQLite stat queries)
|
||||
- `historical_perminute` (historical ledger fetch rate)
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/xrpld/telemetry/MetricsRegistry.h` (4 new gauge members, updated ASCII diagram)
|
||||
- `src/xrpld/telemetry/MetricsRegistry.cpp` (4 new callback registrations, 2 callback extensions)
|
||||
|
||||
**Not implementable inside xrpld**:
|
||||
|
||||
- `connection_count_51233/51234` — OS-level port connection counts from external shell script (`get_connection.sh`)
|
||||
|
||||
**Derived Prometheus metrics**: `xrpld_server_info{metric="server_state"}`, `xrpld_build_info{version="2.4.0"}`, `xrpld_complete_ledgers{bound="start",index="0"}`, `xrpld_db_metrics{metric="db_kb_total"}`, etc.
|
||||
|
||||
**Grafana dashboard**: New panels added to _Node Health_ dashboard (`node-health.json`).
|
||||
|
||||
---
|
||||
|
||||
## Task 9.8: New Grafana Dashboards
|
||||
|
||||
**Objective**: Create Grafana dashboards for the new metric categories.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Create 2 new dashboards:
|
||||
1. **Fee Market & TxQ** (`fee-market`) — TxQ depth/capacity, fee levels, load factor breakdown, fee escalation timeline
|
||||
2. **Job Queue Analysis** (`job-queue`) — Per-job-type rates, queue wait times, execution times, job queue depth
|
||||
|
||||
- Update 2 existing dashboards:
|
||||
1. **Node Health** (`xrpld-statsd-node-health`) — Add NodeStore I/O panels, cache hit rate panels, object instance counts
|
||||
2. **RPC Performance** (`rpc-performance`) — Add per-method RPC breakdown panels
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- New: `docker/telemetry/grafana/dashboards/rippled-fee-market.json`
|
||||
- New: `docker/telemetry/grafana/dashboards/rippled-job-queue.json`
|
||||
- `docker/telemetry/grafana/dashboards/rippled-statsd-node-health.json`
|
||||
- `docker/telemetry/grafana/dashboards/rippled-rpc-perf.json`
|
||||
|
||||
---
|
||||
|
||||
## Task 9.9: Update Documentation
|
||||
|
||||
**Objective**: Update telemetry reference docs with all new metrics.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Update `OpenTelemetryPlan/09-data-collection-reference.md`:
|
||||
- Add new section for OTel SDK-exported metrics (NodeStore, cache, TxQ, PerfLog, CountedObjects, load factors)
|
||||
- Update Grafana dashboard reference table (add 2 new dashboards)
|
||||
- Add Prometheus query examples for new metrics
|
||||
|
||||
- Update `docs/telemetry-runbook.md`:
|
||||
- Add alerting rules for new metrics (NodeStore write_load, TxQ capacity, cache hit rate degradation)
|
||||
- Add troubleshooting entries for new metric categories
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `OpenTelemetryPlan/09-data-collection-reference.md`
|
||||
- `docs/telemetry-runbook.md`
|
||||
|
||||
---
|
||||
|
||||
## Task 9.10: Integration Tests
|
||||
|
||||
**Objective**: Verify all new metrics appear in Prometheus after a test workload.
|
||||
|
||||
**What to do**:
|
||||
|
||||
- Extend the existing telemetry integration test:
|
||||
- Start xrpld with `[telemetry] enabled=1` and `[insight] server=otel`
|
||||
- Submit a batch of RPC calls and transactions
|
||||
- Query Prometheus for each new metric family
|
||||
- Assert non-zero values for: NodeStore reads, cache hit rates, TxQ count, PerfLog RPC counters, object counts, load factors
|
||||
|
||||
- Add unit tests for the `MetricsRegistry` class:
|
||||
- Verify callback registration and deregistration
|
||||
- Verify metric values match `get_counts` JSON output
|
||||
- Verify graceful behavior when telemetry is disabled
|
||||
|
||||
**Key modified files**:
|
||||
|
||||
- `src/test/telemetry/MetricsRegistry_test.cpp` (new)
|
||||
- Existing integration test script (extend assertions)
|
||||
|
||||
---
|
||||
|
||||
## Task 9.11: Validator Health Dashboard (External Dashboard Parity)
|
||||
|
||||
> **Source**: [External Dashboard Parity](../docs/superpowers/specs/2026-03-30-external-dashboard-parity-design.md) — dashboards for Phase 7 metrics inspired by the community [xrpl-validator-dashboard](https://github.com/realgrapedrop/xrpl-validator-dashboard).
|
||||
>
|
||||
> **Upstream**: Phase 7 Tasks 7.9-7.16 (metrics must be emitting).
|
||||
> **Downstream**: Phase 10 (dashboard load checks), Phase 11 (alert rules reference these panels).
|
||||
|
||||
**Objective**: Create a Grafana dashboard for validation agreement, amendment/UNL health, and state tracking.
|
||||
|
||||
**Dashboard**: `validator-health.json`
|
||||
|
||||
| Panel | Type | PromQL |
|
||||
| -------------------------- | ---------- | -------------------------------------------------------------- |
|
||||
| Agreement % (1h) | stat | `xrpld_validation_agreement{metric="agreement_pct_1h"}` |
|
||||
| Agreement % (24h) | stat | `xrpld_validation_agreement{metric="agreement_pct_24h"}` |
|
||||
| Agreements vs Missed (1h) | bargauge | `agreements_1h` and `missed_1h` side by side |
|
||||
| Agreements vs Missed (24h) | bargauge | `agreements_24h` and `missed_24h` side by side |
|
||||
| Validation Rate | stat | `rate(xrpld_validations_sent_total[5m]) * 60` |
|
||||
| Validations Checked Rate | stat | `rate(xrpld_validations_checked_total[5m]) * 60` |
|
||||
| Amendment Blocked | stat | `xrpld_validator_health{metric="amendment_blocked"}` |
|
||||
| UNL Expiry (days) | stat | `xrpld_validator_health{metric="unl_expiry_days"}` |
|
||||
| Validation Quorum | stat | `xrpld_validator_health{metric="validation_quorum"}` |
|
||||
| State Value Timeline | timeseries | `xrpld_state_tracking{metric="state_value"}` |
|
||||
| Time in Current State | stat | `xrpld_state_tracking{metric="time_in_current_state_seconds"}` |
|
||||
| State Changes Rate | stat | `rate(xrpld_state_changes_total[1h])` |
|
||||
| Ledgers Closed Rate | stat | `rate(xrpld_ledgers_closed_total[5m]) * 60` |
|
||||
|
||||
**Dashboard conventions**: `$node` template variable for `exported_instance` filtering, dark theme, matching existing panel sizes and color schemes.
|
||||
|
||||
**Key new files**: `docker/telemetry/grafana/dashboards/rippled-validator-health.json`
|
||||
|
||||
**Exit Criteria**:
|
||||
|
||||
- [ ] All 13 panels render with non-zero data during normal operation
|
||||
- [ ] `$node` filter works correctly for multi-node deployments
|
||||
- [ ] Amendment blocked and UNL expiry panels use color thresholds (red=blocked/expiring)
|
||||
|
||||
---
|
||||
|
||||
## Task 9.12: Peer Quality Dashboard (External Dashboard Parity)
|
||||
|
||||
> **Source**: [External Dashboard Parity](../docs/superpowers/specs/2026-03-30-external-dashboard-parity-design.md)
|
||||
|
||||
**Objective**: Create a Grafana dashboard for peer health aggregates.
|
||||
|
||||
**Dashboard**: `peer-quality.json`
|
||||
|
||||
| Panel | Type | PromQL |
|
||||
| ---------------------- | ---------- | -------------------------------------------------------------- |
|
||||
| P90 Peer Latency | timeseries | `xrpld_peer_quality{metric="peer_latency_p90_ms"}` |
|
||||
| Insane/Diverged Peers | stat | `xrpld_peer_quality{metric="peers_insane_count"}` |
|
||||
| Higher Version Peers % | stat | `xrpld_peer_quality{metric="peers_higher_version_pct"}` |
|
||||
| Upgrade Recommended | stat | `xrpld_peer_quality{metric="upgrade_recommended"}` |
|
||||
| Resource Disconnects | timeseries | `xrpld_Overlay_Peer_Disconnects_Charges` |
|
||||
| Inbound vs Outbound | bargauge | `xrpld_Peer_Finder_Active_Inbound_Peers`, `..._Outbound_Peers` |
|
||||
|
||||
**Key new files**: `docker/telemetry/grafana/dashboards/rippled-peer-quality.json`
|
||||
|
||||
**Exit Criteria**:
|
||||
|
||||
- [ ] All 6 panels render correctly
|
||||
- [ ] P90 latency panel shows trend over time
|
||||
- [ ] Upgrade recommended panel uses color threshold (red=1, green=0)
|
||||
|
||||
---
|
||||
|
||||
## Task 9.13: Ledger Economy Dashboard Panels (External Dashboard Parity)
|
||||
|
||||
> **Source**: [External Dashboard Parity](../docs/superpowers/specs/2026-03-30-external-dashboard-parity-design.md)
|
||||
|
||||
**Objective**: Add "Ledger Economy" row to the existing `node-health.json` dashboard.
|
||||
|
||||
| Panel | Type | PromQL |
|
||||
| -------------------- | ---------- | --------------------------------------------------- |
|
||||
| Base Fee (drops) | stat | `xrpld_ledger_economy{metric="base_fee_xrp"}` |
|
||||
| Reserve Base (drops) | stat | `xrpld_ledger_economy{metric="reserve_base_xrp"}` |
|
||||
| Reserve Inc (drops) | stat | `xrpld_ledger_economy{metric="reserve_inc_xrp"}` |
|
||||
| Ledger Age | stat | `xrpld_ledger_economy{metric="ledger_age_seconds"}` |
|
||||
| Transaction Rate | timeseries | `xrpld_ledger_economy{metric="transaction_rate"}` |
|
||||
|
||||
**Key modified files**: `docker/telemetry/grafana/dashboards/node-health.json`
|
||||
|
||||
**Exit Criteria**:
|
||||
|
||||
- [ ] 5 new panels render correctly in existing dashboard
|
||||
- [ ] Fee values match `server_info` RPC output
|
||||
- [ ] Transaction rate shows smooth trend (not spiky)
|
||||
|
||||
---
|
||||
|
||||
## Exit Criteria
|
||||
|
||||
- [ ] All ~50 new metrics visible in Prometheus via OTLP pipeline
|
||||
- [ ] `MetricsRegistry` class registers/deregisters cleanly with OTel SDK
|
||||
- [ ] Async gauge callbacks execute at 10s intervals without performance impact
|
||||
- [ ] 2 new Grafana dashboards operational (Fee Market, Job Queue)
|
||||
- [ ] 2 existing dashboards updated with new panel groups
|
||||
- [ ] Integration test validates all new metric families are non-zero
|
||||
- [ ] No performance regression (< 0.5% CPU overhead from new callbacks)
|
||||
- [ ] Documentation updated with full new metric inventory
|
||||
- [ ] Validator Health dashboard renders all 13 panels
|
||||
- [ ] Peer Quality dashboard renders all 6 panels
|
||||
- [ ] Ledger Economy panels added to node-health dashboard
|
||||
436
OpenTelemetryPlan/presentation.md
Normal file
436
OpenTelemetryPlan/presentation.md
Normal file
@@ -0,0 +1,436 @@
|
||||
# OpenTelemetry Observability for xrpld
|
||||
|
||||
> Status: Phases 1-8 shipped. Traces, metrics, logs all live via OTel.
|
||||
|
||||
---
|
||||
|
||||
## Slide 1: Introduction
|
||||
|
||||
> **CNCF** = Cloud Native Computing Foundation | **OTel** = OpenTelemetry
|
||||
|
||||
### What is OpenTelemetry?
|
||||
|
||||
CNCF-backed, vendor-neutral framework for **traces, metrics, and logs** with a single SDK and wire protocol (OTLP).
|
||||
|
||||
### Why OTel for xrpld?
|
||||
|
||||
- **End-to-end TX visibility** — submission → consensus → ledger inclusion
|
||||
- **Cross-node correlation** — shared `trace_id` stitches hops without a central coordinator
|
||||
- **Consensus round analysis** — phase timing across validators
|
||||
- **Incident debugging** — correlated traces, metrics, logs for one query
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Node A<br/>tx.receive<br/>trace_id: abc123"] --> B["Node B<br/>tx.relay<br/>trace_id: abc123"] --> C["Node C<br/>tx.validate<br/>trace_id: abc123"] --> D["Node D<br/>ledger.apply<br/>trace_id: abc123"]
|
||||
|
||||
style A fill:#1565c0,stroke:#0d47a1,color:#fff
|
||||
style B fill:#2e7d32,stroke:#1b5e20,color:#fff
|
||||
style C fill:#2e7d32,stroke:#1b5e20,color:#fff
|
||||
style D fill:#e65100,stroke:#bf360c,color:#fff
|
||||
```
|
||||
|
||||
> One trace, four nodes, full lifecycle.
|
||||
|
||||
---
|
||||
|
||||
## Slide 2: Old Stack vs New OTel Stack
|
||||
|
||||
### Side-by-Side
|
||||
|
||||
| Aspect | Before (StatsD + Debug Logs) | After (OTel: Traces + Metrics + Logs) |
|
||||
| ------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------ |
|
||||
| **Metrics** | Beast Insight → StatsD UDP → Graphite | `MetricsRegistry` → OTLP/HTTP → Prometheus |
|
||||
| **Metric inventory** | **~250 metric series** at runtime (28 registrations × overlay traffic categories) | **23 native instruments** × dimensions + RED via spanmetrics |
|
||||
| **Logs** | `beast::Journal` → `debug.log` (grep / tail) | Journal → filelog tail → Loki (structured, queryable) |
|
||||
| **Traces** | None | Telemetry SDK → OTLP → Tempo (cross-node) |
|
||||
| **Correlation** | Timestamp + grep across files | Shared `trace_id` across all 3 signals |
|
||||
| **Format** | Counter/gauge names; free-form log lines | OTLP protobuf; structured records |
|
||||
| **Backend choice** | Locked to StatsD daemon + log files | Vendor-neutral via Collector exporters |
|
||||
| **Cross-node view** | ❌ Not possible | ✅ Native via trace context propagation |
|
||||
| **Histogram p50/p95/p99** | ❌ Counters/gauges only | ✅ Native histograms + spanmetrics |
|
||||
|
||||
### Legacy StatsD Metric Series (~250 total)
|
||||
|
||||
| Category | Series | Notes |
|
||||
| --------------------------- | -------- | ----------------------------------------------------------------------------------- |
|
||||
| **Overlay traffic gauges** | ~224 | 56 `TrafficCount::category` enum × 4 gauges (`Bytes_{In,Out}`, `Messages_{In,Out}`) |
|
||||
| **Peer Finder** | 2 | `Active_{In,Out}bound_Peers` |
|
||||
| **State Accounting** | 10 | `{Disconnected,Connected,Syncing,Tracking,Full}_{duration,transitions}` |
|
||||
| **Ledger** | 4 | `Validated/Published_Ledger_Age`, `mismatch`, `ledger_fetches` |
|
||||
| **RPC / Pathfinding** | 5 | `requests`, `size`, `time`, `pathfind_{fast,full}` |
|
||||
| **JobQueue / IO / Disconn** | 3 | `job_count`, `ios_latency`, `Peer_Disconnects` |
|
||||
| **Total** | **~248** | 28 `make_*` call sites; series count balloons via overlay-category fan-out |
|
||||
|
||||
### Use Case Matrix
|
||||
|
||||
| Scenario | StatsD | Debug Logs | OTel Traces | OTel Metrics | OTel Logs |
|
||||
| ---------------------------------- | ------ | ---------- | ----------- | ------------ | --------- |
|
||||
| "TXs per second?" | ✅ | ❌ | ❌ | ✅ | ❌ |
|
||||
| "Why was this specific TX slow?" | ❌ | ⚠️ | ✅ | ❌ | ⚠️ |
|
||||
| "Which node delayed consensus?" | ❌ | ❌ | ✅ | ❌ | ❌ |
|
||||
| "TX journey across 5 nodes" | ❌ | ❌ | ✅ | ❌ | ❌ |
|
||||
| "Validator error at 14:02" | ❌ | ✅ | ⚠️ | ❌ | ✅ |
|
||||
| "Reproduce rare assertion / crash" | ❌ | ✅ | ❌ | ❌ | ✅ |
|
||||
| "p99 RPC latency by method" | ⚠️ | ❌ | ⚠️ | ✅ | ❌ |
|
||||
|
||||
> Old stack: 2 signals, no correlation, single node. New stack: 3 signals, `trace_id` everywhere, cross-node native.
|
||||
|
||||
---
|
||||
|
||||
## Slide 3: OTel vs Open-Source Alternatives
|
||||
|
||||
| Feature | OpenTelemetry | Jaeger | Zipkin | SkyWalking | Pinpoint | Prometheus |
|
||||
| ------------------- | --------------- | ------------- | --------------- | ---------- | ---------- | ---------- |
|
||||
| **Tracing** | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
|
||||
| **Metrics** | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ |
|
||||
| **Logs** | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ |
|
||||
| **C++ SDK** | ✅ Official | ⚠️ Deprecated | ⚠️ Unmaintained | ❌ | ❌ | ✅ |
|
||||
| **Vendor neutral** | ✅ Primary goal | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| **Instrumentation** | Manual + Auto | Manual | Manual | Auto-first | Auto-first | Manual |
|
||||
| **Backend** | Any (exporters) | Self | Self | Self | Self | Self |
|
||||
| **CNCF Status** | Incubating | Graduated | — | Incubating | — | Graduated |
|
||||
|
||||
> Only actively maintained, full-signal C++ option. Backend-agnostic — Tempo/Prometheus/Loki/Elastic/commercial all work without code change.
|
||||
|
||||
---
|
||||
|
||||
## Slide 4: Architecture (Current)
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol over HTTP/gRPC
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph xrpld["xrpld Node"]
|
||||
direction TB
|
||||
Surfaces["RPC · TX · Consensus · Peer · Ledger · Job"]
|
||||
SDK["Telemetry SDK + MetricsRegistry"]
|
||||
Journal["beast::Journal → debug.log<br/>(trace_id/span_id injected)"]
|
||||
Surfaces --> SDK
|
||||
Surfaces --> Journal
|
||||
end
|
||||
|
||||
SDK -->|"OTLP/HTTP :4318<br/>traces + metrics"| Collector["OTel Collector"]
|
||||
Journal -->|"filelog tail"| Collector
|
||||
|
||||
Collector --> Tempo["Tempo<br/>(traces)"]
|
||||
Collector --> Prom["Prometheus<br/>(metrics)"]
|
||||
Collector --> Loki["Loki<br/>(logs)"]
|
||||
|
||||
Tempo --> Grafana["Grafana<br/>(15 dashboards)"]
|
||||
Prom --> Grafana
|
||||
Loki --> Grafana
|
||||
|
||||
style xrpld fill:#424242,stroke:#212121,color:#fff
|
||||
style SDK fill:#2e7d32,stroke:#1b5e20,color:#fff
|
||||
style Journal fill:#1565c0,stroke:#0d47a1,color:#fff
|
||||
style Collector fill:#e65100,stroke:#bf360c,color:#fff
|
||||
style Grafana fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
```
|
||||
|
||||
| Component | Role |
|
||||
| ---------------------- | --------------------------------------------------- |
|
||||
| Telemetry SDK | Span creation, trace context, OTLP traces export |
|
||||
| MetricsRegistry | RPC/job/peer/consensus counters, gauges, histograms |
|
||||
| beast::Journal filelog | `debug.log` tailed by Collector, parsed → Loki |
|
||||
| OTel Collector | Receive OTLP + filelog; route to Tempo/Prom/Loki |
|
||||
| Spanmetrics connector | Derives RED metrics from spans (Prometheus) |
|
||||
|
||||
---
|
||||
|
||||
## Slide 5: Signal Coverage
|
||||
|
||||
| Surface | Traces (Spans) | Metrics (OTLP) | Logs (Journal Partition) |
|
||||
| ------------------ | --------------------------------------------------------------- | ---------------------------------------------- | ------------------------------ |
|
||||
| **RPC** | `rpc.request` + handler spans | request count, latency p50/p95/p99, error rate | `RPC*` |
|
||||
| **Transactions** | `tx.receive`, `tx.validate`, `tx.relay`, `tx.apply` | TX/sec by result, fee escalation gauges | `TxQ`, `LedgerMaster` |
|
||||
| **Consensus** | `consensus.round`, `proposal.send/recv`, `validation.send/recv` | round duration, phase histograms, mode gauge | `Consensus`, `LedgerConsensus` |
|
||||
| **Peer / Overlay** | `peer.send`, `peer.receive` per message type | peer count, bytes/sec by msg type, suppression | `Overlay`, `PeerImp` |
|
||||
| **Ledger** | `ledger.close`, `ledger.apply` | close time, TX count, ledger index gauge | `LedgerMaster` |
|
||||
| **Job Queue** | (sampled per type) | queue depth, queue/run duration histograms | `JobQueue` |
|
||||
|
||||
> ~30 distinct span kinds, ~80 metric series, structured logs from 50+ partitions.
|
||||
|
||||
---
|
||||
|
||||
## Slide 6: Context Propagation
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant NodeA as Node A
|
||||
participant NodeB as Node B
|
||||
|
||||
Client->>NodeA: Submit TX (no context)
|
||||
Note over NodeA: Create trace_id: abc123<br/>span: tx.receive
|
||||
NodeA->>NodeB: Relay TX (TraceContext field, ~29B)
|
||||
Note over NodeB: Link trace_id: abc123<br/>span: tx.relay (parent: A)
|
||||
```
|
||||
|
||||
| Carrier | Mechanism |
|
||||
| --------------------- | ------------------------------------------ |
|
||||
| HTTP / WebSocket RPC | W3C `traceparent` header |
|
||||
| P2P protobuf | `TraceContext` extension field per message |
|
||||
| Internal job dispatch | Thread-local context + `SpanGuard` |
|
||||
|
||||
| Field | Size | Description |
|
||||
| ------------- | --------- | ------------------------------------- |
|
||||
| `trace_id` | 16 bytes | Trace correlation key |
|
||||
| `span_id` | 8 bytes | Parent span on receiver |
|
||||
| `trace_flags` | 1 byte | Sampling decision |
|
||||
| `trace_state` | 0-4 bytes | Optional vendor data |
|
||||
| **Total** | **~29 B** | Per traced P2P message (~1-6% of msg) |
|
||||
|
||||
---
|
||||
|
||||
## Slide 7: Performance Overhead
|
||||
|
||||
| Metric | Overhead | Driver |
|
||||
| ----------------- | ---------- | --------------------------------------------------- |
|
||||
| **CPU** | 1-3% | ~4 μs/TX span work (~2% at 25 TPS baseline) |
|
||||
| **Memory** | ~10 MB | SDK statics + worker stack + 2048-span export queue |
|
||||
| **Network** | 10-50 KB/s | OTLP export + 29 B P2P context per traced msg |
|
||||
| **Latency (p99)** | <2% | TX path dominates; RPC and consensus negligible |
|
||||
|
||||
### Kill Switches
|
||||
|
||||
1. `enabled=0` in `xrpld.cfg` → instant disable, no restart
|
||||
2. Build with `XRPL_ENABLE_TELEMETRY=OFF` → zero overhead (no-op stubs)
|
||||
3. Reduce `sampling_ratio` → linear export reduction
|
||||
|
||||
> Derivations and per-component cost tables: see [03-implementation-strategy.md §3.5.4](./03-implementation-strategy.md#354-performance-data-sources).
|
||||
|
||||
---
|
||||
|
||||
## Slide 8: Sampling — Head vs Tail
|
||||
|
||||
| | Head Sampling | Tail Sampling |
|
||||
| ------------------------ | --------------------------------- | -------------------------------------- |
|
||||
| **Where** | Inside xrpld (SDK) | OTel Collector (external) |
|
||||
| **Decision time** | Trace start (random coin flip) | Trace end (after all spans buffered) |
|
||||
| **Knows trace content?** | No | Yes — error, latency, span kind |
|
||||
| **xrpld overhead** | Lowest (drop = no-op) | Higher (export 100%) |
|
||||
| **Captures all errors?** | No | **Yes** (status_code policy) |
|
||||
| **Captures slow ops?** | No | **Yes** (latency policy) |
|
||||
| **Config** | `xrpld.cfg`: `sampling_ratio=0.1` | `tail_sampling` processor in collector |
|
||||
| **Best for** | Steady-state high volume | Anomaly + error retention |
|
||||
|
||||
### Recommended Layered Strategy
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
xrpld["xrpld<br/>sampling_ratio=1.0<br/>(export all)"] -->|"100%"| col["Collector<br/>tail_sampling:<br/>errors + slow + 10% random"]
|
||||
col -->|"~15-20% kept"| tempo["Tempo storage"]
|
||||
|
||||
style xrpld fill:#424242,stroke:#212121,color:#fff
|
||||
style col fill:#1565c0,stroke:#0d47a1,color:#fff
|
||||
style tempo fill:#2e7d32,stroke:#1b5e20,color:#fff
|
||||
```
|
||||
|
||||
> If Collector resource pressure: drop `sampling_ratio` to 0.5 — still enough trace volume for tail decisions.
|
||||
|
||||
---
|
||||
|
||||
## Slide 9: Data Collection & Privacy
|
||||
|
||||
### Collected (operational metadata)
|
||||
|
||||
| Category | Attributes |
|
||||
| ----------- | -------------------------------------------------------------------- |
|
||||
| Transaction | `tx.hash`, `tx.type`, `tx.result`, `tx.fee`, `ledger_index` |
|
||||
| Consensus | `round`, `phase`, `mode`, `proposers`, `duration_ms` |
|
||||
| RPC | `command`, `version`, `status`, `duration_ms` |
|
||||
| Peer | `peer.id` (public key), `latency_ms`, `message.type`, `message.size` |
|
||||
| Ledger | `ledger.hash`, `ledger.index`, `close_time`, `tx_count` |
|
||||
| Job | `job.type`, `queue_ms`, `worker` |
|
||||
|
||||
### NOT Collected (hard exclusions)
|
||||
|
||||
> ❌ Private keys · ❌ Account balances · ❌ Transaction amounts · ❌ Raw payloads · ❌ Personal data · ⚙️ IP addresses (configurable)
|
||||
|
||||
### Privacy Mechanisms
|
||||
|
||||
| Mechanism | Description |
|
||||
| ---------------------- | --------------------------------------------------------- |
|
||||
| Account hashing | `xrpl.tx.account` hashed at Collector before storage |
|
||||
| Configurable redaction | Sensitive attributes excluded via Collector config |
|
||||
| Sampling | 10% default reduces exposure |
|
||||
| Local control | Operator owns Collector → backend pipeline |
|
||||
| No raw payloads | Span attributes are metadata only, never message contents |
|
||||
|
||||
> Principle: telemetry records **operational metadata** — never financial or personal content.
|
||||
|
||||
---
|
||||
|
||||
## Slide 10: Implementation Timeline
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
title OpenTelemetry Rollout
|
||||
dateFormat YYYY-MM-DD
|
||||
axisFormat Week %W
|
||||
|
||||
section Done
|
||||
Phase 1 Core Infra :done, p1, 2024-01-01, 2w
|
||||
Phase 2 RPC Tracing :done, p2, after p1, 2w
|
||||
Phase 3 TX Tracing :done, p3, after p2, 2w
|
||||
Phase 4 Consensus :done, p4, after p3, 2w
|
||||
Phase 5 Docs/Deploy :done, p5, after p4, 1w
|
||||
Phase 6 StatsD Bridge :done, p6, after p5, 1w
|
||||
Phase 7 Native OTel Metrics :done, p7, after p6, 2w
|
||||
Phase 8 Log-Trace Correlation :done, p8, after p7, 1w
|
||||
Phase 9 Metric Gap Fill :active, p9, after p8, 2w
|
||||
|
||||
section Future
|
||||
Phase 10 Workload Validation :p10, after p9, 2w
|
||||
Phase 11 3rd-Party Pipelines :p11, after p10, 3w
|
||||
```
|
||||
|
||||
| Phase | Focus | Status |
|
||||
| ----- | ------------------------------------------- | ------- |
|
||||
| 1 | SDK integration, Telemetry, Config | ✅ Done |
|
||||
| 2 | RPC handler spans, HTTP context | ✅ Done |
|
||||
| 3 | TX spans, P2P protobuf context | ✅ Done |
|
||||
| 4 | Consensus rounds, proposal/validation | ✅ Done |
|
||||
| 5 | Runbook, dashboards, deployment | ✅ Done |
|
||||
| 6 | StatsD bridge (interim) | ✅ Done |
|
||||
| 7 | Native OTel metrics (replace Beast Insight) | ✅ Done |
|
||||
| 8 | Log-trace correlation (Loki) | ✅ Done |
|
||||
| 9 | Internal metric gap fill | ✅ Done |
|
||||
|
||||
---
|
||||
|
||||
## Slide 11: Current State — What Shipped
|
||||
|
||||
### By Signal
|
||||
|
||||
| Signal | Backend | Status | Notes |
|
||||
| ----------- | ---------- | ------ | -------------------------------------------------------- |
|
||||
| **Traces** | Tempo | ✅ | All 6 surfaces instrumented; cross-node propagation live |
|
||||
| **Metrics** | Prometheus | ✅ | Native OTLP; Beast Insight retired |
|
||||
| **Logs** | Loki | ✅ | filelog tailing `debug.log`; `trace_id` injected |
|
||||
|
||||
### By Surface
|
||||
|
||||
| Surface | Spans Live | Metrics Live | Notes |
|
||||
| -------------- | ---------- | ------------ | --------------------------------------------------- |
|
||||
| RPC | ✅ | ✅ | Handler + pathfinding + TxQ |
|
||||
| Transactions | ✅ | ✅ | Receive, validate, relay, apply |
|
||||
| Consensus | ✅ | ✅ | Round + proposal/validation send+receive (Phase 4a) |
|
||||
| Peer / Overlay | ✅ | ✅ | Per-msg-type send/receive |
|
||||
| Ledger | ✅ | ✅ | Close + apply |
|
||||
| Job Queue | ✅ | ✅ | Queue depth + duration histograms |
|
||||
|
||||
### Stack Live
|
||||
|
||||
| Component | Version |
|
||||
| -------------------------- | ------- |
|
||||
| OTel Collector (contrib) | 0.121.0 |
|
||||
| Grafana Tempo | 2.7.2 |
|
||||
| Grafana Loki | 3.4.2 |
|
||||
| Prometheus | latest |
|
||||
| Grafana | 11.5.2 |
|
||||
| **Dashboards provisioned** | **15** |
|
||||
|
||||
---
|
||||
|
||||
## Slide 12: Future Phases
|
||||
|
||||
### Phase 10 — Synthetic Workload Validation
|
||||
|
||||
| Aspect | Detail |
|
||||
| ----------- | ------------------------------------------------------------------ |
|
||||
| Goal | Drive instrumented surfaces under reproducible load |
|
||||
| Why | Validate dashboards, catch regressions, measure overhead at scale |
|
||||
| Deliverable | Workload generator + assertion suite (RPC/TX/peer churn scenarios) |
|
||||
| Effort | ~2 weeks |
|
||||
|
||||
### Phase 11 — Admin-RPC Receiver (`xrpl_*` metrics)
|
||||
|
||||
| Aspect | Detail |
|
||||
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Goal | Custom Go OTel Collector receiver polls xrpld admin RPC, emits `xrpl_*` Prometheus metrics |
|
||||
| Why | Admin-RPC-only data has no native export — every consumer reinvents JSON-RPC polling |
|
||||
| Scope | `validators` (UNL, listed keys), `feature` (amendments), `peers` (per-peer detail), `amm_info`, `book_offers`, `fee` (detail tiers) |
|
||||
| Excluded | `server_info` / `get_counts` basics — Phase 9 (#6513) already ships `xrpld_server_info` + 14 gauges/histograms natively from in-process state |
|
||||
| Deliverable | Go receiver plugin + custom Collector binary + 4 Grafana dashboards (UNL, amendments, AMM, DEX) + Prometheus alerts |
|
||||
| Effort | ~3 weeks |
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
rpc["xrpld admin RPC<br/>(validators, feature, peers,<br/>amm_info, book_offers, fee)"] -->|JSON-RPC poll| recv["Custom Go receiver<br/>(in Collector)"]
|
||||
recv -->|xrpl_* metrics| prom["Prometheus"]
|
||||
prom --> graf["Grafana dashboards"]
|
||||
|
||||
style rpc fill:#2e7d32,stroke:#1b5e20,color:#fff
|
||||
style recv fill:#1565c0,stroke:#0d47a1,color:#fff
|
||||
style prom fill:#e65100,stroke:#bf360c,color:#fff
|
||||
style graf fill:#6a1b9a,stroke:#4a148c,color:#fff
|
||||
```
|
||||
|
||||
> Phase 11 fills the gap above Phase 9 — data only reachable via admin RPC, not via in-process metric callbacks.
|
||||
|
||||
---
|
||||
|
||||
## Slide 11: External Dashboard Parity (Phase 7+)
|
||||
|
||||
### Bridging Community Monitoring into Native OTel
|
||||
|
||||
The community [xrpl-validator-dashboard](https://github.com/realgrapedrop/xrpl-validator-dashboard) provides 86 metrics for validator operators. We integrated the 29 missing metrics natively into the OTel pipeline.
|
||||
|
||||
### New Metric Categories
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph "New Observable Gauges"
|
||||
VH["Validator Health<br/>amendment_blocked, UNL expiry,<br/>quorum"]
|
||||
PQ["Peer Quality<br/>P90 latency, insane peers,<br/>version awareness"]
|
||||
LE["Ledger Economy<br/>fees, reserves, tx rate,<br/>ledger age"]
|
||||
ST["State Tracking<br/>state value 0-6,<br/>time in state"]
|
||||
VA["Validation Agreement<br/>1h/24h agreement %,<br/>agreements, misses"]
|
||||
end
|
||||
|
||||
subgraph "Counters"
|
||||
C1["ledgers_closed_total"]
|
||||
C2["validations_sent_total"]
|
||||
C3["state_changes_total"]
|
||||
end
|
||||
|
||||
style VH fill:#1565c0,color:#fff
|
||||
style PQ fill:#2e7d32,color:#fff
|
||||
style LE fill:#e65100,color:#fff
|
||||
style ST fill:#6a1b9a,color:#fff
|
||||
style VA fill:#c62828,color:#fff
|
||||
style C1 fill:#37474f,color:#fff
|
||||
style C2 fill:#37474f,color:#fff
|
||||
style C3 fill:#37474f,color:#fff
|
||||
```
|
||||
|
||||
### ValidationTracker — Agreement Computation
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as RCLConsensus
|
||||
participant VT as ValidationTracker
|
||||
participant MR as MetricsRegistry
|
||||
participant P as Prometheus
|
||||
|
||||
C->>VT: recordOurValidation(hash, seq)
|
||||
Note over VT: Stores pending event
|
||||
C->>VT: recordNetworkValidation(hash, seq)
|
||||
Note over VT: Marks network validated
|
||||
MR->>VT: reconcile() [every 10s]
|
||||
Note over VT: After 8s grace period:<br/>both validated → agreed<br/>only one → missed<br/>5min late repair window
|
||||
MR->>P: Export agreement_pct_1h/24h
|
||||
```
|
||||
|
||||
### New Grafana Dashboards
|
||||
|
||||
| Dashboard | Key Panels |
|
||||
| ---------------- | --------------------------------------------------- |
|
||||
| Validator Health | Agreement %, amendment blocked, quorum, state value |
|
||||
| Peer Quality | P90 latency, version awareness, upgrade recommended |
|
||||
| Ledger Economy | Base fee, reserves, ledger age, transaction rate |
|
||||
|
||||
---
|
||||
|
||||
_End of Presentation_
|
||||
239
OpenTelemetryPlan/secure-OTel.md
Normal file
239
OpenTelemetryPlan/secure-OTel.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# Securing OpenTelemetry Against Trace Context Spoofing
|
||||
|
||||
> **Part of**: [OpenTelemetry Implementation Plan](./OpenTelemetryPlan.md) — see also [Design Decisions § Privacy](./02-design-decisions.md#244-privacy--sensitive-data-policy) (what we don't collect) and [Configuration Reference § 5.5](./05-configuration-reference.md#55-opentelemetry-collector-configuration) (collector base config).
|
||||
|
||||
Trace context spoofing (or poisoning) occurs when untrusted actors inject tampered or stale trace IDs into your system. If these requests are processed, the spans are appended to historical trace buckets, stretching trace durations, ruining p99 latency metrics, and breaking Grafana dashboards.
|
||||
|
||||
This guide outlines two categories of defense: mitigating tampered contexts and locking down the OpenTelemetry (OTel) Collector to trusted clients only.
|
||||
|
||||
---
|
||||
|
||||
## Part 1: Mitigating Tampered Trace Contexts
|
||||
|
||||
### 1. Perimeter Defense: Strip Headers at the API Gateway
|
||||
|
||||
The most effective way to prevent spoofing from external sources is to treat your API Gateway (Envoy, NGINX, AWS ALB) as a hard boundary. Strip incoming W3C tracing headers (`traceparent`, `tracestate`) from public traffic so the gateway is forced to generate a fresh, legitimate `trace_id`.
|
||||
|
||||
**NGINX Example (Stripping Headers):**
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location {
|
||||
# Clear out untrusted incoming trace headers
|
||||
proxy_set_header traceparent "";
|
||||
proxy_set_header tracestate "";
|
||||
|
||||
proxy_pass http://backend_service;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **2. Timestamp-Anchored Trace IDs and OTTL Filtering**
|
||||
|
||||
If you use a custom trace ID generator that embeds a timestamp in the first few bytes (like AWS X-Ray or UUIDv7), you can use the OTel Collector's OpenTelemetry Transform Language (OTTL) to detect anomalies.
|
||||
**Collector Configuration (Conceptual OTTL Filter):**
|
||||
|
||||
```yaml
|
||||
processors:
|
||||
filter/stale_traces:
|
||||
error_mode: ignore
|
||||
traces:
|
||||
span:
|
||||
# Example: Drop spans where the start time is significantly different
|
||||
# from an expected parameter or embedded timestamp logic.
|
||||
# Note: Standard W3C trace IDs do not contain timestamps by default.
|
||||
- 'Keep out-of-bounds spans: time.sub(start_time, now()) > duration("1h")'
|
||||
```
|
||||
|
||||
## **Part 2: Restricting Access to the OTel Collector**
|
||||
|
||||
Locking down the Collector ensures that only authenticated, trusted clients can submit telemetry data.
|
||||
|
||||
### **Approach A: Network Layer Security (Kubernetes Network Policies)**
|
||||
|
||||
Ensure your Collector is not exposed to the public internet. If running in Kubernetes, use a NetworkPolicy to restrict ingress traffic to specific namespaces.
|
||||
**Kubernetes NetworkPolicy Example:**
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-internal-otel
|
||||
namespace: observability
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: opentelemetry-collector
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
environment: production
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 4317 # gRPC
|
||||
- protocol: TCP
|
||||
port: 4318 # HTTP
|
||||
```
|
||||
|
||||
### **Approach B: Transport Layer Security (Mutual TLS / mTLS)**
|
||||
|
||||
Require clients to present a valid cryptographic certificate to connect to the Collector.
|
||||
**Collector Configuration (mTLS):**
|
||||
|
||||
```yaml
|
||||
receivers:
|
||||
otlp:
|
||||
protocols:
|
||||
grpc:
|
||||
endpoint: 0.0.0.0:4317
|
||||
tls:
|
||||
client_ca_file: /certs/client_ca.pem # CA that signs trusted client certs
|
||||
cert_file: /certs/collector.pem
|
||||
key_file: /certs/collector.key
|
||||
auth_type: require_and_verify_client_cert # Rejects unauthorized clients
|
||||
```
|
||||
|
||||
### **Approach C: Application Layer Authentication (Basic Auth Extension)**
|
||||
|
||||
Use the Collector's extension system to require an API key or Basic Auth credentials.
|
||||
**Collector Configuration (Basic Auth):**
|
||||
|
||||
```yaml
|
||||
extensions:
|
||||
basicauth/collector:
|
||||
htpasswd:
|
||||
inline: |
|
||||
# username:trusted-client, password:SecurePassword123
|
||||
trusted-client:$apr1$4v8p76o6$DMTX5Wv6uOmrFAZp2X1N1.
|
||||
|
||||
receivers:
|
||||
otlp:
|
||||
protocols:
|
||||
grpc:
|
||||
endpoint: 0.0.0.0:4317
|
||||
auth:
|
||||
authenticator: basicauth/collector
|
||||
|
||||
processors:
|
||||
batch:
|
||||
|
||||
exporters:
|
||||
otlp:
|
||||
endpoint: my-backend-storage:4317
|
||||
|
||||
service:
|
||||
extensions: [basicauth/collector]
|
||||
pipelines:
|
||||
traces:
|
||||
receivers: [otlp]
|
||||
processors: [batch]
|
||||
exporters: [otlp]
|
||||
```
|
||||
|
||||
**Client Setup (Environment Variables):**
|
||||
Developers must pass the authentication header using the standard OTel SDK environment variables:
|
||||
|
||||
```bash
|
||||
# Base64 encoded "trusted-client:SecurePassword123"
|
||||
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic dHJ1c3RlZC1jbGllbnQ6U2VjdXJlUGFzc3dvcmQxMjM="
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Available routes to build on top of: https://github.com/XRPLF/rippled/pull/6425#discussion_r3234751995
|
||||
|
||||
---
|
||||
|
||||
# Analysis: Applying the Guide to xrpld
|
||||
|
||||
The guide above is written for HTTP-fronted web services. xrpld is a P2P node daemon, so the threat model and the applicable defenses differ. This section captures how each approach maps to xrpld and the chosen direction.
|
||||
|
||||
## Threat Model
|
||||
|
||||
xrpld has **two distinct attack surfaces**, not one. The original guide conflates them under "trace context spoofing"; for xrpld they need separate defenses.
|
||||
|
||||
| Surface | Attacker | Vector | Defense |
|
||||
| ----------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------- |
|
||||
| **Collector ingress** (xrpld → collector) | Anyone who can reach `4317`/`4318` on the collector host | Forged OTLP traffic, telemetry exfiltration, DoS on collector | mTLS + network policy |
|
||||
| **Peer trace context** (peer → xrpld) | Malicious peer in the XRPL overlay | Crafted `protocol::TraceContext` field inside peer protobuf messages (TMTransaction, consensus, etc.) — used to forge `trace_id`/`span_id`, pollute p99, attach spans to historical traces | Validate + rate-limit at the receive boundary |
|
||||
|
||||
**Deployment context:** Across-network. xrpld nodes (potentially run by external operators or in different DCs) ship telemetry to a centrally-hosted collector across an untrusted network. The collector is NOT on the same host or private VPC as every node.
|
||||
|
||||
```
|
||||
┌── peer (untrusted) ── TMTransaction{trace_context} ──▶ xrpld
|
||||
│ │
|
||||
│ [validate + rate-limit]
|
||||
│ │
|
||||
│ ▼
|
||||
│ SpanGuard (clean)
|
||||
│ │
|
||||
│ │ OTLP/gRPC
|
||||
│ │ + mTLS
|
||||
│ ▼
|
||||
└───────────────────────────────────────── [require_and_verify_client_cert]
|
||||
OTel Collector
|
||||
(in private subnet, NetPol)
|
||||
```
|
||||
|
||||
## Part 1 Applicability — Peer Trace-Context Validation
|
||||
|
||||
The guide's NGINX header stripping and OTTL stale-span filtering target HTTP gateways and post-hoc cleanup. Neither fits xrpld directly:
|
||||
|
||||
- **NGINX header stripping** — N/A. There is no HTTP gateway between peers and xrpld; trace context arrives inside protobuf peer messages (`protocol::TraceContext`), not as W3C `traceparent` headers. See [src/xrpld/telemetry/PropagationHelpers.h](../src/xrpld/telemetry/PropagationHelpers.h).
|
||||
- **OTTL stale-span filtering** — Weak fit. Post-hoc cleanup at the collector loses peer identity (you can't tell _which_ peer poisoned the trace). Validation at the receive site is stronger.
|
||||
|
||||
**xrpld-specific Part 1 mitigations:**
|
||||
|
||||
1. **Validate extracted context at the boundary** in [src/xrpld/telemetry/ConsensusReceiveTracing.h](../src/xrpld/telemetry/ConsensusReceiveTracing.h) and any other peer-message receive site. Reject if `trace_id` is all-zero, wrong length, or fails W3C format checks. Treat invalid context as "no propagated context" — start a fresh span — rather than dropping the message.
|
||||
2. **Per-peer sample rate limiting** so a hostile peer cannot flood the collector with spans bearing a fabricated `trace_id`. Use probabilistic sampling on the receive path keyed by peer identity.
|
||||
|
||||
## Part 2 — Comparison of Collector Hardening Approaches
|
||||
|
||||
Evaluated for the across-network deployment shape:
|
||||
|
||||
| Approach | Across-network fit | Cost | Verdict |
|
||||
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ---------------------------------- |
|
||||
| **A. NetworkPolicy / firewall** | Necessary baseline (don't expose `4317`/`4318` to the internet), but insufficient on its own when traffic genuinely crosses networks — you cannot NetworkPolicy the public internet. | Cheap. | **Defense-in-depth, not primary.** |
|
||||
| **B. mTLS** | Strongest fit. Every xrpld node holds a client cert; collector verifies with `require_and_verify_client_cert`. Encrypts in transit (raw OTLP over the internet leaks transaction patterns and validator identity). Compromised node = revoke one cert, no shared secret to rotate everywhere. | Cert issuance + rotation pipeline. | **Primary.** |
|
||||
| **C. Basic Auth** | Worst shape for this topology. Single shared password across all xrpld nodes — one leaked node config compromises the whole fleet. Doesn't encrypt; you'd need TLS underneath anyway, at which point you're 80% of the way to mTLS. | Cheap to set up, expensive to operate (rotation across N operators). | **Skip.** |
|
||||
|
||||
## Decision
|
||||
|
||||
**Primary defense:** mTLS (Approach B) on the collector's OTLP receivers, with `auth_type: require_and_verify_client_cert`.
|
||||
|
||||
**Defense-in-depth:** NetworkPolicy / firewall rules (Approach A) so `4317`/`4318` are never reachable from outside the expected operator subnets even if mTLS were misconfigured.
|
||||
|
||||
**Skipped:** Basic Auth (Approach C) — wrong shape for an across-network, multi-operator topology.
|
||||
|
||||
**Plus xrpld-specific Part 1 work:** trace-context validation and per-peer rate limiting at peer-message receive sites.
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Choice | Rationale |
|
||||
| -------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Cert source for mTLS | **Reuse XRPL node identity key** | One identity per node, no separate PKI to operate. Fits XRPL's existing trust model; requires small CA tooling step to derive/sign the OTel client cert from the node key. |
|
||||
| Part 1 scope | **Include in this spec** | Collector hardening and peer trace-context validation share one threat model. Coherent design doc; can still be split into multiple PRs at implementation. |
|
||||
| Dev impact | **Production-only** | Local `docker/telemetry/docker-compose.yml` keeps `insecure: true` and no auth for fast iteration. Only production deployment manifests gain mTLS. Accepted risk: minor dev/prod drift, mitigated by integration tests against a TLS-enabled collector in CI. |
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- NGINX/Envoy header stripping (no HTTP gateway in front of xrpld-to-collector traffic).
|
||||
- OTTL stale-span filtering at the collector (weaker than source validation; loses peer identity).
|
||||
- Local development docker-compose hardening.
|
||||
- Telemetry backend (Tempo) hardening — separate concern, downstream of the collector.
|
||||
|
||||
## Next Step
|
||||
|
||||
Write this up as a design doc with full sections covering:
|
||||
|
||||
1. Threat model & architecture (this section, expanded)
|
||||
2. Collector hardening — mTLS config, NetworkPolicy
|
||||
3. Cert pipeline — deriving OTel client cert from XRPL node key
|
||||
4. Peer trace-context validation — receive-site checks in `ConsensusReceiveTracing.h`
|
||||
5. Per-peer span rate limiting
|
||||
6. Testing & rollout
|
||||
22
README.md
22
README.md
@@ -8,11 +8,11 @@ The [XRP Ledger](https://xrpl.org/) is a decentralized cryptographic ledger powe
|
||||
|
||||
[XRP](https://xrpl.org/xrp.html) is a public, counterparty-free crypto-asset native to the XRP Ledger, and is designed as a gas token for network services and to bridge different currencies. XRP is traded on the open-market and is available for anyone to access. The XRP Ledger was created in 2012 with a finite supply of 100 billion units of XRP.
|
||||
|
||||
## rippled
|
||||
## xrpld
|
||||
|
||||
The server software that powers the XRP Ledger is called `rippled` and is available in this repository under the permissive [ISC open-source license](LICENSE.md). The `rippled` server software is written primarily in C++ and runs on a variety of platforms. The `rippled` server software can run in several modes depending on its [configuration](https://xrpl.org/rippled-server-modes.html).
|
||||
The server software that powers the XRP Ledger is called `xrpld` and is available in this repository under the permissive [ISC open-source license](LICENSE.md). The `xrpld` server software is written primarily in C++ and runs on a variety of platforms. The `xrpld` server software can run in several modes depending on its [configuration](https://xrpl.org/rippled-server-modes.html).
|
||||
|
||||
If you are interested in running an **API Server** (including a **Full History Server**), take a look at [Clio](https://github.com/XRPLF/clio). (rippled Reporting Mode has been replaced by Clio.)
|
||||
If you are interested in running an **API Server** (including a **Full History Server**), take a look at [Clio](https://github.com/XRPLF/clio). (xrpld Reporting Mode has been replaced by Clio.)
|
||||
|
||||
### Build from Source
|
||||
|
||||
@@ -41,19 +41,19 @@ If you are interested in running an **API Server** (including a **Full History S
|
||||
|
||||
Here are some good places to start learning the source code:
|
||||
|
||||
- Read the markdown files in the source tree: `src/ripple/**/*.md`.
|
||||
- Read the markdown files in the source tree: `src/xrpld/**/*.md`.
|
||||
- Read [the levelization document](.github/scripts/levelization) to get an idea of the internal dependency graph.
|
||||
- In the big picture, the `main` function constructs an `ApplicationImp` object, which implements the `Application` virtual interface. Almost every component in the application takes an `Application&` parameter in its constructor, typically named `app` and stored as a member variable `app_`. This allows most components to depend on any other component.
|
||||
|
||||
### Repository Contents
|
||||
|
||||
| Folder | Contents |
|
||||
| :--------- | :----------------------------------------------- |
|
||||
| `./bin` | Scripts and data files for Ripple integrators. |
|
||||
| `./Builds` | Platform-specific guides for building `rippled`. |
|
||||
| `./docs` | Source documentation files and doxygen config. |
|
||||
| `./cfg` | Example configuration files. |
|
||||
| `./src` | Source code. |
|
||||
| Folder | Contents |
|
||||
| :--------- | :--------------------------------------------- |
|
||||
| `./bin` | Scripts and data files for XRPL developers. |
|
||||
| `./Builds` | Platform-specific guides for building `xrpld`. |
|
||||
| `./docs` | Source documentation files and doxygen config. |
|
||||
| `./cfg` | Example configuration files. |
|
||||
| `./src` | Source code. |
|
||||
|
||||
Some of the directories under `src` are external repositories included using
|
||||
git-subtree. See those directories' README files for more details.
|
||||
|
||||
115
SECURITY.md
115
SECURITY.md
@@ -6,7 +6,7 @@ For more details on operating an XRP Ledger server securely, please visit https:
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Software constantly evolves. In order to focus resources, we only generally only accept vulnerability reports that affect recent and current versions of the software. We always accept reports for issues present in the **master**, **release** or **develop** branches, and with proposed, [open pull requests](https://github.com/ripple/rippled/pulls).
|
||||
Software constantly evolves. In order to focus resources, we generally only accept vulnerability reports that affect recent and current versions of the software. We always accept reports for issues present in the **master**, **release** or **develop** branches, and with proposed, [open pull requests](https://github.com/XRPLF/rippled/pulls).
|
||||
|
||||
## Identifying and Reporting Vulnerabilities
|
||||
|
||||
@@ -22,117 +22,10 @@ Responsible investigation includes, but isn't limited to, the following:
|
||||
- Not targeting physical security measures, or attempting to use social engineering, spam, distributed denial of service (DDOS) attacks, etc.
|
||||
- Investigating bugs in a way that makes a reasonable, good faith effort not to be disruptive or harmful to the XRP Ledger and the broader ecosystem.
|
||||
|
||||
### Responsible Disclosure
|
||||
|
||||
If you discover a vulnerability or potential threat, or if you _think_
|
||||
you have, please reach out by dropping an email using the contact
|
||||
information below.
|
||||
|
||||
Your report should include the following:
|
||||
|
||||
- Your contact information (typically, an email address);
|
||||
- The description of the vulnerability;
|
||||
- The attack scenario (if any);
|
||||
- The steps to reproduce the vulnerability;
|
||||
- Any other relevant details or artifacts, including code, scripts or patches.
|
||||
|
||||
In your email, please describe the issue or potential threat. If possible, include a "repro" (code that can reproduce the issue) or describe the best way to reproduce and replicate the issue. Please make your report as detailed and comprehensive as possible.
|
||||
|
||||
For more information on responsible disclosure, please read this [Wikipedia article](https://en.wikipedia.org/wiki/Responsible_disclosure).
|
||||
|
||||
## Report Handling Process
|
||||
|
||||
Please report the bug directly to us and limit further disclosure. If you want to prove that you knew the bug as of a given time, consider using a cryptographic pre-commitment: hash the content of your report and publish the hash on a medium of your choice (e.g. on Twitter or as a memo in a transaction) as "proof" that you had written the text at a given point in time.
|
||||
|
||||
Once we receive a report, we:
|
||||
|
||||
1. Assign two people to independently evaluate the report;
|
||||
2. Consider their recommendations;
|
||||
3. If action is necessary, formulate a plan to address the issue;
|
||||
4. Communicate privately with the reporter to explain our plan.
|
||||
5. Prepare, test and release a version which fixes the issue; and
|
||||
6. Announce the vulnerability publicly.
|
||||
|
||||
We will triage and respond to your disclosure within 24 hours. Beyond that, we will work to analyze the issue in more detail, formulate, develop and test a fix.
|
||||
|
||||
While we commit to responding with 24 hours of your initial report with our triage assessment, we cannot guarantee a response time for the remaining steps. We will communicate with you throughout this process, letting you know where we are and keeping you updated on the timeframe.
|
||||
|
||||
## Bug Bounty Program
|
||||
|
||||
[Ripple](https://ripple.com) is generously sponsoring a bug bounty program for vulnerabilities in [`rippled`](https://github.com/XRPLF/rippled) (and other related projects, like [`xrpl.js`](https://github.com/XRPLF/xrpl.js), [`xrpl-py`](https://github.com/XRPLF/xrpl-py), [`xrpl4j`](https://github.com/XRPLF/xrpl4j)).
|
||||
[Ripple](https://ripple.com) is generously sponsoring a bug bounty program for vulnerabilities in [`xrpld`](https://github.com/XRPLF/rippled) (and other related projects, like [`Clio`](https://github.com/XRPLF/clio), [`xrpl.js`](https://github.com/XRPLF/xrpl.js), [`xrpl-py`](https://github.com/XRPLF/xrpl-py), [`xrpl4j`](https://github.com/XRPLF/xrpl4j)).
|
||||
|
||||
This program allows us to recognize and reward individuals or groups that identify and report bugs. In summary, in order to qualify for a bounty, the bug must be:
|
||||
This program allows us to recognize and reward individuals or groups that identify and report bugs.
|
||||
|
||||
1. **In scope**. Only bugs in software under the scope of the program qualify. Currently, that means `rippled`, `xrpl.js`, `xrpl-py`, `xrpl4j`.
|
||||
2. **Relevant**. A security issue, posing a danger to user funds, privacy, or the operation of the XRP Ledger.
|
||||
3. **Original and previously unknown**. Bugs that are already known and discussed in public do not qualify. Previously reported bugs, even if publicly unknown, are not eligible.
|
||||
4. **Specific**. We welcome general security advice or recommendations, but we cannot pay bounties for that.
|
||||
5. **Fixable**. There has to be something we can do to permanently fix the problem. Note that bugs in other people’s software may still qualify in some cases. For example, if you find a bug in a library that we use which can compromise the security of software that is in scope and we can get it fixed, you may qualify for a bounty.
|
||||
6. **Unused**. If you use the exploit to attack the XRP Ledger, you do not qualify for a bounty. If you report a vulnerability used in an ongoing or past attack and there is specific, concrete evidence that suggests you are the attacker we reserve the right not to pay a bounty.
|
||||
|
||||
The amount paid varies dramatically. Vulnerabilities that are harmless on their own, but could form part of a critical exploit will usually receive a bounty. Full-blown exploits can receive much higher bounties. Please don’t hold back partial vulnerabilities while trying to construct a full-blown exploit. We will pay a bounty to anyone who reports a complete chain of vulnerabilities even if they have reported each component of the exploit separately and those vulnerabilities have been fixed in the meantime. However, to qualify for a the full bounty, you must to have been the first to report each of the partial exploits.
|
||||
|
||||
### Contacting Us
|
||||
|
||||
To report a qualifying bug, please send a detailed report to:
|
||||
|
||||
| Email Address | bugs@ripple.com |
|
||||
| :-----------: | :-------------------------------------------------- |
|
||||
| Short Key ID | `0xA9F514E0` |
|
||||
| Long Key ID | `0xD900855AA9F514E0` |
|
||||
| Fingerprint | `B72C 0654 2F2A E250 2763 A268 D900 855A A9F5 14E0` |
|
||||
|
||||
The full PGP key for this address, which is also available on several key servers (e.g. on [keyserver.ubuntu.com](https://keyserver.ubuntu.com)), is:
|
||||
|
||||
```
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
mQINBGkSZAQBEACprU199OhgdsOsygNjiQV4msuN3vDOUooehL+NwfsGfW79Tbqq
|
||||
Q2u7uQ3NZjW+M2T4nsDwuhkr7pe7xSReR5W8ssaczvtUyxkvbMClilcgZ2OSCAuC
|
||||
N9tzJsqOqkwBvXoNXkn//T2jnPz0ZU2wSF+NrEibq5FeuyGdoX3yXXBxq9pW9HzK
|
||||
HkQll63QSl6BzVSGRQq+B6lGgaZGLwf3mzmIND9Z5VGLNK2jKynyz9z091whNG/M
|
||||
kV+E7/r/bujHk7WIVId07G5/COTXmSr7kFnNEkd2Umw42dkgfiNKvlmJ9M7c1wLK
|
||||
KbL9Eb4ADuW6rRc5k4s1e6GT8R4/VPliWbCl9SE32hXH8uTkqVIFZP2eyM5WRRHs
|
||||
aKzitkQG9UK9gcb0kdgUkxOvvgPHAe5IuZlcHFzU4y0dBbU1VEFWVpiLU0q+IuNw
|
||||
5BRemeHc59YNsngkmAZ+/9zouoShRusZmC8Wzotv75C2qVBcjijPvmjWAUz0Zunm
|
||||
Lsr+O71vqHE73pERjD07wuD/ISjiYRYYE/bVrXtXLZijC7qAH4RE3nID+2ojcZyO
|
||||
/2jMQvt7un56RsGH4UBHi3aBHi9bUoDGCXKiQY981cEuNaOxpou7Mh3x/ONzzSvk
|
||||
sTV6nl1LOZHykN1JyKwaNbTSAiuyoN+7lOBqbV04DNYAHL88PrT21P83aQARAQAB
|
||||
tB1SaXBwbGUgTGFicyA8YnVnc0ByaXBwbGUuY29tPokCTgQTAQgAOBYhBLcsBlQv
|
||||
KuJQJ2OiaNkAhVqp9RTgBQJpEmQEAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheA
|
||||
AAoJENkAhVqp9RTgBzgP/i7y+aDWl1maig1XMdyb+o0UGusumFSW4Hmj278wlKVv
|
||||
usgLPihYgHE0PKrv6WRyKOMC1tQEcYYN93M+OeQ1vFhS2YyURq6RCMmh4zq/awXG
|
||||
uZbG36OURB5NH8lGBOHiN/7O+nY0CgenBT2JWm+GW3nEOAVOVm4+r5GlpPlv+Dp1
|
||||
NPBThcKXFMnH73++NpSQoDzTfRYHPxhDAX3jkLi/moXfSanOLlR6l94XNNN0jBHW
|
||||
Quao0rzf4WSXq9g6AS224xhAA5JyIcFl8TX7hzj5HaFn3VWo3COoDu4U7H+BM0fl
|
||||
85yqiMQypp7EhN2gxpMMWaHY5TFM85U/bFXFYfEgihZ4/gt4uoIzsNI9jlX7mYvG
|
||||
KFdDij+oTlRsuOxdIy60B3dKcwOH9nZZCz0SPsN/zlRWgKzK4gDKdGhFkU9OlvPu
|
||||
94ZqscanoiWKDoZkF96+sjgfjkuHsDK7Lwc1Xi+T4drHG/3aVpkYabXox+lrKB/S
|
||||
yxZjeqOIQzWPhnLgCaLyvsKo5hxKzL0w3eURu8F3IS7RgOOlljv4M+Me9sEVcdNV
|
||||
aN3/tQwbaomSX1X5D5YXqhBwC3rU3wXwamsscRTGEpkV+JCX6KUqGP7nWmxCpAly
|
||||
FL05XuOd5SVHJjXLeuje0JqLUpN514uL+bThWwDbDTdAdwW3oK/2WbXz7IfJRLBj
|
||||
uQINBGkSZAQBEADdI3SL2F72qkrgFqXWE6HSRBu9bsAvTE5QrRPWk7ux6at537r4
|
||||
S4sIw2dOwLvbyIrDgKNq3LQ5wCK88NO/NeCOFm4AiCJSl3pJHXYnTDoUxTrrxx+o
|
||||
vSRI4I3fHEql/MqzgiAb0YUezjgFdh3vYheMPp/309PFbOLhiFqEcx80Mx5h06UH
|
||||
gDzu1qNj3Ec+31NLic5zwkrAkvFvD54d6bqYR3SEgMau6aYEewpGHbWBi2pLqSi2
|
||||
lQcAeOFixqGpTwDmAnYR8YtjBYepy0MojEAdTHcQQlOYSDk4q4elG+io2N8vECfU
|
||||
rD6ORecN48GXdZINYWTAdslrUeanmBdgQrYkSpce8TSghgT9P01SNaXxmyaehVUO
|
||||
lqI4pcg5G2oojAE8ncNS3TwDtt7daTaTC3bAdr4PXDVAzNAiewjMNZPB7xidkDGQ
|
||||
Y4W1LxTMXyJVWxehYOH7tsbBRKninlfRnLgYzmtIbNRAAvNcsxU6ihv3AV0WFknN
|
||||
YbSzotEv1Xq/5wk309x8zCDe+sP0cQicvbXafXmUzPAZzeqFg+VLFn7F9MP1WGlW
|
||||
B1u7VIvBF1Mp9Nd3EAGBAoLRdRu+0dVWIjPTQuPIuD9cCatJA0wVaKUrjYbBMl88
|
||||
a12LixNVGeSFS9N7ADHx0/o7GNT6l88YbaLP6zggUHpUD/bR+cDN7vllIQARAQAB
|
||||
iQI2BBgBCAAgFiEEtywGVC8q4lAnY6Jo2QCFWqn1FOAFAmkSZAQCGwwACgkQ2QCF
|
||||
Wqn1FOAfAA/8CYq4p0p4bobY20CKEMsZrkBTFJyPDqzFwMeTjgpzqbD7Y3Qq5QCK
|
||||
OBbvY02GWdiIsNOzKdBxiuam2xYP9WHZj4y7/uWEvT0qlPVmDFu+HXjoJ43oxwFd
|
||||
CUp2gMuQ4cSL3X94VRJ3BkVL+tgBm8CNY0vnTLLOO3kum/R69VsGJS1JSGUWjNM+
|
||||
4qwS3mz+73xJu1HmERyN2RZF/DGIZI2PyONQQ6aH85G1Dd2ohu2/DBAkQAMBrPbj
|
||||
FrbDaBLyFhODxU3kTWqnfLlaElSm2EGdIU2yx7n4BggEa//NZRMm5kyeo4vzhtlQ
|
||||
YIVUMLAOLZvnEqDnsLKp+22FzNR/O+htBQC4lPywl53oYSALdhz1IQlcAC1ru5KR
|
||||
XPzhIXV6IIzkcx9xNkEclZxmsuy5ERXyKEmLbIHAlzFmnrldlt2ZgXDtzaorLmxj
|
||||
klKibxd5tF50qOpOivz+oPtFo7n+HmFa1nlVAMxlDCUdM0pEVeYDKI5zfVwalyhZ
|
||||
NnjpakdZSXMwgc7NP/hH9buF35hKDp7EckT2y3JNYwHsDdy1icXN2q40XZw5tSIn
|
||||
zkPWdu3OUY8PISohN6Pw4h0RH4ZmoX97E8sEfmdKaT58U4Hf2aAv5r9IWCSrAVqY
|
||||
u5jvac29CzQR9Kal0A+8phHAXHNFD83SwzIC0syaT9ficAguwGH8X6Q=
|
||||
=nGuD
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
```
|
||||
We have partnered with Bugcrowd to manage this program. It is a private program, and security researchers can participate based on invitation. If you need access to the program, please email bugs@ripple.com with your Bugcrowd handle or Bugcrowd registered email, and we will get you added to the program. Once you have been added, please submit vulnerability reports through Bugcrowd, not by email. The detailed bug bounty policy is available on the Bugcrowd website.
|
||||
|
||||
161
bin/check-tools.sh
Executable file
161
bin/check-tools.sh
Executable file
@@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# check-tools.sh — verify the xrpld development tooling is present and runnable.
|
||||
#
|
||||
# Works on Linux, macOS, and Windows (Git Bash / MSYS). For every expected tool
|
||||
# it runs a version probe, collecting anything that is missing or fails to run,
|
||||
# and prints a summary at the end (exiting non-zero if anything is missing).
|
||||
#
|
||||
# The tool set is platform-aware:
|
||||
# - Linux: the full Nix CI environment (see nix/packages.nix, nix/ci-env.nix),
|
||||
# with GCC, Clang and the sanitizer/coverage tooling. This script is
|
||||
# run during the Nix Docker image build (nix/docker/Dockerfile), so
|
||||
# the Linux list is kept in sync with that environment.
|
||||
# - macOS: the same tooling, minus GCC/g++/gcov/mold
|
||||
# - 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.
|
||||
#
|
||||
# Environment variables:
|
||||
# CI if set, skip the tools above when on macOS.
|
||||
# CHECK_TOOLS_SKIP_CLONE if set, skip the git-over-HTTPS connectivity check.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
missing=()
|
||||
checked=0
|
||||
|
||||
# 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.
|
||||
check() {
|
||||
local name="$1"
|
||||
shift
|
||||
local -a probe=("$@")
|
||||
if [ "${#probe[@]}" -eq 0 ]; then
|
||||
probe=("${name}" --version)
|
||||
fi
|
||||
|
||||
echo "Checking ${name}..."
|
||||
checked=$((checked + 1))
|
||||
if "${probe[@]}" | head -n 1; then
|
||||
printf ' [ ok ] %s\n' "${name}"
|
||||
else
|
||||
printf ' [MISS] %s\n' "${name}"
|
||||
missing+=("${name}")
|
||||
fi
|
||||
}
|
||||
|
||||
case "$(uname -s)" in
|
||||
Linux*) os=linux ;;
|
||||
Darwin*) os=macos ;;
|
||||
MINGW* | MSYS* | CYGWIN*) os=windows ;;
|
||||
*)
|
||||
echo "Unknown OS: $(uname -s)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "Detected OS: ${os} ($(uname -s) $(uname -m))"
|
||||
echo
|
||||
echo "Core build tools:"
|
||||
check cmake
|
||||
check conan
|
||||
check git
|
||||
if [ "${os}" = "windows" ]; then
|
||||
check python python --version
|
||||
else
|
||||
check python3
|
||||
fi
|
||||
|
||||
# The full development toolchain. Available from Nix on Linux and macOS; on
|
||||
# Windows these are typically not installed, so they are skipped.
|
||||
if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
|
||||
echo
|
||||
echo "Development tooling:"
|
||||
check ccache
|
||||
check clang
|
||||
check clang++
|
||||
check ClangBuildAnalyzer
|
||||
check curl
|
||||
check file
|
||||
check less
|
||||
check make
|
||||
check netstat which netstat
|
||||
check ninja
|
||||
check perl
|
||||
check pkg-config
|
||||
check vim
|
||||
check zip
|
||||
|
||||
# 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-format
|
||||
check dot
|
||||
check doxygen
|
||||
check gcovr
|
||||
check gh
|
||||
check git-cliff
|
||||
check git-lfs
|
||||
check gpg
|
||||
# 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
|
||||
fi
|
||||
fi
|
||||
|
||||
# GCC is the default compiler on Linux. macOS uses the system Apple Clang
|
||||
# instead, so GCC/g++/gcov are not expected there.
|
||||
if [ "${os}" = "linux" ]; then
|
||||
echo
|
||||
echo "GCC toolchain:"
|
||||
check gcc
|
||||
check g++
|
||||
check gcov
|
||||
|
||||
echo
|
||||
echo "Mold:"
|
||||
check mold
|
||||
fi
|
||||
|
||||
if [ "${os}" = "windows" ]; then
|
||||
echo
|
||||
echo "Note: on Windows the C++ compiler is MSVC, which is provided"
|
||||
echo " separately (e.g. via Visual Studio) and is not checked here."
|
||||
fi
|
||||
|
||||
# A simple test to verify that git can clone a repository over HTTPS
|
||||
# (i.e. the CA bundle is wired up). Clone to a temp dir and clean up.
|
||||
if [ -n "${CHECK_TOOLS_SKIP_CLONE:-}" ]; then
|
||||
echo
|
||||
echo "Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set)."
|
||||
else
|
||||
echo
|
||||
echo "Connectivity check:"
|
||||
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'
|
||||
else
|
||||
printf ' [MISS] git clone over HTTPS\n'
|
||||
missing+=("git-https-clone")
|
||||
fi
|
||||
rm -rf "${tmp_clone}"
|
||||
fi
|
||||
|
||||
echo
|
||||
if [ "${#missing[@]}" -eq 0 ]; then
|
||||
echo "All ${checked} checked tools are present and runnable."
|
||||
else
|
||||
echo "Missing or non-functional tools (${#missing[@]} of ${checked}):" >&2
|
||||
for tool in "${missing[@]}"; do
|
||||
echo " - ${tool}" >&2
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,83 +1,71 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ $# -ne 1 || "$1" == "--help" || "$1" == "-h" ]]
|
||||
then
|
||||
name=$( basename $0 )
|
||||
cat <<- USAGE
|
||||
Usage: $name <username>
|
||||
if [[ $# -ne 1 || "$1" == "--help" || "$1" == "-h" ]]; then
|
||||
name=$(basename $0)
|
||||
cat <<-USAGE
|
||||
Usage: $name <username>
|
||||
|
||||
Where <username> is the Github username of the upstream repo. e.g. XRPLF
|
||||
Where <username> is the Github username of the upstream repo. e.g. XRPLF
|
||||
USAGE
|
||||
exit 0
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create upstream remotes based on origin
|
||||
shift
|
||||
user="$1"
|
||||
# Get the origin URL. Expect it be an SSH-style URL
|
||||
origin=$( git remote get-url origin )
|
||||
if [[ "${origin}" == "" ]]
|
||||
then
|
||||
echo Invalid origin remote >&2
|
||||
exit 1
|
||||
origin=$(git remote get-url origin)
|
||||
if [[ "${origin}" == "" ]]; then
|
||||
echo Invalid origin remote >&2
|
||||
exit 1
|
||||
fi
|
||||
# echo "Origin: ${origin}"
|
||||
# Parse the origin
|
||||
ifs_orig="${IFS}"
|
||||
IFS=':' read remote originpath <<< "${origin}"
|
||||
IFS=':' read remote originpath <<<"${origin}"
|
||||
# echo "Remote: ${remote}, Originpath: ${originpath}"
|
||||
IFS='@' read sshuser server <<< "${remote}"
|
||||
IFS='@' read sshuser server <<<"${remote}"
|
||||
# echo "SSHUser: ${sshuser}, Server: ${server}"
|
||||
IFS='/' read originuser repo <<< "${originpath}"
|
||||
IFS='/' read originuser repo <<<"${originpath}"
|
||||
# echo "Originuser: ${originuser}, Repo: ${repo}"
|
||||
if [[ "${sshuser}" == "" || "${server}" == "" || "${originuser}" == ""
|
||||
|| "${repo}" == "" ]]
|
||||
then
|
||||
echo "Can't parse origin URL: ${origin}" >&2
|
||||
exit 1
|
||||
if [[ "${sshuser}" == "" || "${server}" == "" || "${originuser}" == "" || "${repo}" == "" ]]; then
|
||||
echo "Can't parse origin URL: ${origin}" >&2
|
||||
exit 1
|
||||
fi
|
||||
upstream="https://${server}/${user}/${repo}"
|
||||
upstreampush="${remote}:${user}/${repo}"
|
||||
upstreamgroup="upstream upstream-push"
|
||||
current=$( git remote get-url upstream 2>/dev/null )
|
||||
currentpush=$( git remote get-url upstream-push 2>/dev/null )
|
||||
currentgroup=$( git config remotes.upstreams )
|
||||
if [[ "${current}" == "${upstream}" ]]
|
||||
then
|
||||
echo "Upstream already set up correctly. Skip"
|
||||
elif [[ -n "${current}" && "${current}" != "${upstream}" &&
|
||||
"${current}" != "${upstreampush}" ]]
|
||||
then
|
||||
echo "Upstream already set up as: ${current}. Skip"
|
||||
current=$(git remote get-url upstream 2>/dev/null)
|
||||
currentpush=$(git remote get-url upstream-push 2>/dev/null)
|
||||
currentgroup=$(git config remotes.upstreams)
|
||||
if [[ "${current}" == "${upstream}" ]]; then
|
||||
echo "Upstream already set up correctly. Skip"
|
||||
elif [[ -n "${current}" && "${current}" != "${upstream}" && "${current}" != "${upstreampush}" ]]; then
|
||||
echo "Upstream already set up as: ${current}. Skip"
|
||||
else
|
||||
if [[ "${current}" == "${upstreampush}" ]]
|
||||
then
|
||||
echo "Upstream set to dangerous push URL. Update."
|
||||
_run git remote rename upstream upstream-push || \
|
||||
_run git remote remove upstream
|
||||
currentpush=$( git remote get-url upstream-push 2>/dev/null )
|
||||
fi
|
||||
_run git remote add upstream "${upstream}"
|
||||
if [[ "${current}" == "${upstreampush}" ]]; then
|
||||
echo "Upstream set to dangerous push URL. Update."
|
||||
_run git remote rename upstream upstream-push ||
|
||||
_run git remote remove upstream
|
||||
currentpush=$(git remote get-url upstream-push 2>/dev/null)
|
||||
fi
|
||||
_run git remote add upstream "${upstream}"
|
||||
fi
|
||||
|
||||
if [[ "${currentpush}" == "${upstreampush}" ]]
|
||||
then
|
||||
echo "upstream-push already set up correctly. Skip"
|
||||
elif [[ -n "${currentpush}" && "${currentpush}" != "${upstreampush}" ]]
|
||||
then
|
||||
echo "upstream-push already set up as: ${currentpush}. Skip"
|
||||
if [[ "${currentpush}" == "${upstreampush}" ]]; then
|
||||
echo "upstream-push already set up correctly. Skip"
|
||||
elif [[ -n "${currentpush}" && "${currentpush}" != "${upstreampush}" ]]; then
|
||||
echo "upstream-push already set up as: ${currentpush}. Skip"
|
||||
else
|
||||
_run git remote add upstream-push "${upstreampush}"
|
||||
_run git remote add upstream-push "${upstreampush}"
|
||||
fi
|
||||
|
||||
if [[ "${currentgroup}" == "${upstreamgroup}" ]]
|
||||
then
|
||||
echo "Upstreams group already set up correctly. Skip"
|
||||
elif [[ -n "${currentgroup}" && "${currentgroup}" != "${upstreamgroup}" ]]
|
||||
then
|
||||
echo "Upstreams group already set up as: ${currentgroup}. Skip"
|
||||
if [[ "${currentgroup}" == "${upstreamgroup}" ]]; then
|
||||
echo "Upstreams group already set up correctly. Skip"
|
||||
elif [[ -n "${currentgroup}" && "${currentgroup}" != "${upstreamgroup}" ]]; then
|
||||
echo "Upstreams group already set up as: ${currentgroup}. Skip"
|
||||
else
|
||||
_run git config --add remotes.upstreams "${upstreamgroup}"
|
||||
_run git config --add remotes.upstreams "${upstreamgroup}"
|
||||
fi
|
||||
|
||||
_run git fetch --jobs=$(nproc) upstreams
|
||||
|
||||
@@ -1,61 +1,56 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ $# -lt 3 || "$1" == "--help" || "$1" = "-h" ]]
|
||||
then
|
||||
name=$( basename $0 )
|
||||
cat <<- USAGE
|
||||
Usage: $name workbranch base/branch user/branch [user/branch [...]]
|
||||
if [[ $# -lt 3 || "$1" == "--help" || "$1" = "-h" ]]; then
|
||||
name=$(basename $0)
|
||||
cat <<-USAGE
|
||||
Usage: $name workbranch base/branch user/branch [user/branch [...]]
|
||||
|
||||
* workbranch will be created locally from base/branch
|
||||
* base/branch and user/branch may be specified as user:branch to allow
|
||||
easy copying from Github PRs
|
||||
* Remotes for each user must already be set up
|
||||
* workbranch will be created locally from base/branch
|
||||
* base/branch and user/branch may be specified as user:branch to allow
|
||||
easy copying from Github PRs
|
||||
* Remotes for each user must already be set up
|
||||
USAGE
|
||||
exit 0
|
||||
exit 0
|
||||
fi
|
||||
|
||||
work="$1"
|
||||
shift
|
||||
|
||||
branches=( $( echo "${@}" | sed "s/:/\//" ) )
|
||||
branches=($(echo "${@}" | sed "s/:/\//"))
|
||||
base="${branches[0]}"
|
||||
unset branches[0]
|
||||
|
||||
set -e
|
||||
|
||||
users=()
|
||||
for b in "${branches[@]}"
|
||||
do
|
||||
users+=( $( echo $b | cut -d/ -f1 ) )
|
||||
for b in "${branches[@]}"; do
|
||||
users+=($(echo $b | cut -d/ -f1))
|
||||
done
|
||||
|
||||
users=( $( printf '%s\n' "${users[@]}" | sort -u ) )
|
||||
users=($(printf '%s\n' "${users[@]}" | sort -u))
|
||||
|
||||
git fetch --multiple upstreams "${users[@]}"
|
||||
git checkout -B "$work" --no-track "$base"
|
||||
|
||||
for b in "${branches[@]}"
|
||||
do
|
||||
git merge --squash "${b}"
|
||||
git commit -S # Use the commit message provided on the PR
|
||||
for b in "${branches[@]}"; do
|
||||
git merge --squash "${b}"
|
||||
git commit -S # Use the commit message provided on the PR
|
||||
done
|
||||
|
||||
# Make sure the commits look right
|
||||
git log --show-signature "$base..HEAD"
|
||||
|
||||
parts=( $( echo $base | sed "s/\// /" ) )
|
||||
parts=($(echo $base | sed "s/\// /"))
|
||||
repo="${parts[0]}"
|
||||
b="${parts[1]}"
|
||||
push=$repo
|
||||
if [[ "$push" == "upstream" ]]
|
||||
then
|
||||
push="upstream-push"
|
||||
if [[ "$push" == "upstream" ]]; then
|
||||
push="upstream-push"
|
||||
fi
|
||||
if [[ "$repo" == "upstream" ]]
|
||||
then
|
||||
repo="upstreams"
|
||||
if [[ "$repo" == "upstream" ]]; then
|
||||
repo="upstreams"
|
||||
fi
|
||||
cat << PUSH
|
||||
cat <<PUSH
|
||||
|
||||
-------------------------------------------------------------------
|
||||
This script will not push. Verify everything is correct, then push
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ $# -ne 3 || "$1" == "--help" || "$1" = "-h" ]]
|
||||
then
|
||||
name=$( basename $0 )
|
||||
cat <<- USAGE
|
||||
Usage: $name workbranch base/branch version
|
||||
if [[ $# -ne 3 || "$1" == "--help" || "$1" = "-h" ]]; then
|
||||
name=$(basename $0)
|
||||
cat <<-USAGE
|
||||
Usage: $name workbranch base/branch version
|
||||
|
||||
* workbranch will be created locally from base/branch. If it exists,
|
||||
it will be reused, so make sure you don't overwrite any work.
|
||||
* base/branch may be specified as user:branch to allow easy copying
|
||||
from Github PRs.
|
||||
* workbranch will be created locally from base/branch. If it exists,
|
||||
it will be reused, so make sure you don't overwrite any work.
|
||||
* base/branch may be specified as user:branch to allow easy copying
|
||||
from Github PRs.
|
||||
USAGE
|
||||
exit 0
|
||||
exit 0
|
||||
fi
|
||||
|
||||
work="$1"
|
||||
shift
|
||||
|
||||
base=$( echo "$1" | sed "s/:/\//" )
|
||||
base=$(echo "$1" | sed "s/:/\//")
|
||||
shift
|
||||
|
||||
version=$1
|
||||
@@ -29,17 +28,16 @@ git fetch upstreams
|
||||
|
||||
git checkout -B "${work}" --no-track "${base}"
|
||||
|
||||
push=$( git rev-parse --abbrev-ref --symbolic-full-name '@{push}' \
|
||||
2>/dev/null ) || true
|
||||
if [[ "${push}" != "" ]]
|
||||
then
|
||||
echo "Warning: ${push} may already exist."
|
||||
push=$(git rev-parse --abbrev-ref --symbolic-full-name '@{push}' \
|
||||
2>/dev/null) || true
|
||||
if [[ "${push}" != "" ]]; then
|
||||
echo "Warning: ${push} may already exist."
|
||||
fi
|
||||
|
||||
build=$( find -name BuildInfo.cpp )
|
||||
sed 's/\(^.*versionString =\).*$/\1 "'${version}'"/' ${build} > version.cpp && \
|
||||
diff "${build}" version.cpp && exit 1 || \
|
||||
mv -vi version.cpp ${build}
|
||||
build=$(find -name BuildInfo.cpp)
|
||||
sed 's/\(^.*versionString =\).*$/\1 "'${version}'"/' ${build} >version.cpp &&
|
||||
diff "${build}" version.cpp && exit 1 ||
|
||||
mv -vi version.cpp ${build}
|
||||
|
||||
git diff
|
||||
|
||||
@@ -49,7 +47,7 @@ git commit -S -m "Set version to ${version}"
|
||||
|
||||
git log --oneline --first-parent ${base}^..
|
||||
|
||||
cat << PUSH
|
||||
cat <<PUSH
|
||||
|
||||
-------------------------------------------------------------------
|
||||
This script will not push. Verify everything is correct, then push
|
||||
|
||||
113
bin/install-sanitizer-libs.sh
Executable file
113
bin/install-sanitizer-libs.sh
Executable file
@@ -0,0 +1,113 @@
|
||||
#!/bin/bash
|
||||
# Install sanitizer runtime libraries required to run binaries compiled with:
|
||||
# -fsanitize=address → libasan.so.8
|
||||
# -fsanitize=thread → libtsan.so.2
|
||||
# -fsanitize=undefined → libubsan.so.1
|
||||
#
|
||||
# The exact SONAMEs required depend on the compiler toolchain used to build the
|
||||
# test binaries (see nix/ci-env.nix). If the toolchain is bumped and SONAMEs
|
||||
# change, update the list below (or detect them from the binaries).
|
||||
#
|
||||
# Supported base images:
|
||||
# debian:bookworm
|
||||
# ubuntu:20.04
|
||||
# rhel:9
|
||||
# nixos/nix — tests are skipped; this script is not called
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [ ! -f /etc/os-release ]; then
|
||||
echo "ERROR: /etc/os-release not found; cannot detect OS" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
. /etc/os-release
|
||||
|
||||
echo "Detected OS: ${ID} ${VERSION_ID:-}"
|
||||
|
||||
case "${ID}" in
|
||||
ubuntu | debian | rhel | centos | rocky | almalinux)
|
||||
echo "Supported OS detected: ${ID}"
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: unsupported OS '${ID}'. Supported: debian, ubuntu, rhel-family" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
function preinstall() {
|
||||
case "${ID}" in
|
||||
ubuntu)
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends \
|
||||
gnupg \
|
||||
software-properties-common
|
||||
add-apt-repository -y ppa:ubuntu-toolchain-r/test
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function install() {
|
||||
case "${ID}" in
|
||||
debian | ubuntu)
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends \
|
||||
libasan8 \
|
||||
libtsan2 \
|
||||
libubsan1
|
||||
;;
|
||||
|
||||
rhel | centos | rocky | almalinux)
|
||||
dnf install -y \
|
||||
libasan8 \
|
||||
libtsan2 \
|
||||
libubsan
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function postinstall() {
|
||||
# Don't clear cache in non-CI environments
|
||||
if [ -z "${CI:-}" ]; then
|
||||
echo "Not running in CI environment; skipping cache cleanup"
|
||||
return
|
||||
fi
|
||||
|
||||
case "${ID}" in
|
||||
debian | ubuntu)
|
||||
apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
;;
|
||||
|
||||
rhel | centos | rocky | almalinux)
|
||||
dnf clean -y all
|
||||
rm -rf /var/cache/dnf/*
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function verify() {
|
||||
# Verify that every expected library is now resolvable by the dynamic linker.
|
||||
missing=0
|
||||
for lib in libasan.so.8 libtsan.so.2 libubsan.so.1; do
|
||||
if ldconfig -p | grep -q "${lib}"; then
|
||||
echo "OK: ${lib} found"
|
||||
else
|
||||
echo "ERROR: ${lib} not found after installation" >&2
|
||||
missing=$((missing + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${missing}" -ne 0 ]; then
|
||||
echo "ERROR: ${missing} library/libraries missing" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
preinstall
|
||||
install
|
||||
postinstall
|
||||
verify
|
||||
|
||||
echo "All sanitizer runtime libraries installed successfully."
|
||||
212
bin/pre-commit/clang_tidy_check.py
Executable file
212
bin/pre-commit/clang_tidy_check.py
Executable file
@@ -0,0 +1,212 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Pre-commit hook that runs clang-tidy on changed files using run-clang-tidy."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
|
||||
HEADER_EXTENSIONS = {".h", ".hpp", ".ipp"}
|
||||
SOURCE_EXTENSIONS = {".cpp"}
|
||||
INCLUDE_RE = re.compile(r"^\s*#\s*include\s*[<\"]([^>\"]+)[>\"]")
|
||||
|
||||
|
||||
def find_run_clang_tidy() -> str | None:
|
||||
for candidate in ("run-clang-tidy-21", "run-clang-tidy"):
|
||||
if path := shutil.which(candidate):
|
||||
return path
|
||||
return None
|
||||
|
||||
|
||||
def find_build_dir(repo_root: Path) -> Path | None:
|
||||
for name in (".build", "build"):
|
||||
candidate = repo_root / name
|
||||
if (candidate / "compile_commands.json").exists():
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def build_include_graph(build_dir: Path, repo_root: Path) -> tuple[dict, set]:
|
||||
"""
|
||||
Scan all files reachable from compile_commands.json and build an inverted include graph.
|
||||
|
||||
Returns:
|
||||
inverted: header_path -> set of files that include it
|
||||
source_files: set of all TU paths from compile_commands.json
|
||||
"""
|
||||
with open(build_dir / "compile_commands.json") as f:
|
||||
db = json.load(f)
|
||||
|
||||
source_files = {Path(e["file"]).resolve() for e in db}
|
||||
include_roots = [repo_root / "include", repo_root / "src"]
|
||||
inverted: dict[Path, set[Path]] = defaultdict(set)
|
||||
|
||||
to_scan: set[Path] = set(source_files)
|
||||
scanned: set[Path] = set()
|
||||
|
||||
while to_scan:
|
||||
file = to_scan.pop()
|
||||
if file in scanned or not file.exists():
|
||||
continue
|
||||
scanned.add(file)
|
||||
|
||||
content = file.read_text()
|
||||
|
||||
for line in content.splitlines():
|
||||
m = INCLUDE_RE.match(line)
|
||||
if not m:
|
||||
continue
|
||||
for root in include_roots:
|
||||
candidate = (root / m.group(1)).resolve()
|
||||
if candidate.exists():
|
||||
inverted[candidate].add(file)
|
||||
if candidate not in scanned:
|
||||
to_scan.add(candidate)
|
||||
break
|
||||
|
||||
return inverted, source_files
|
||||
|
||||
|
||||
def find_tus_for_headers(
|
||||
headers: list[Path],
|
||||
inverted: dict[Path, set[Path]],
|
||||
source_files: set[Path],
|
||||
) -> set[Path]:
|
||||
"""
|
||||
For each header, pick one TU that transitively includes it.
|
||||
Prefers a TU whose stem matches the header's stem, otherwise picks the first found.
|
||||
"""
|
||||
result: set[Path] = set()
|
||||
|
||||
for header in headers:
|
||||
preferred: Path | None = None
|
||||
visited: set[Path] = {header}
|
||||
stack: list[Path] = [header]
|
||||
|
||||
while stack:
|
||||
h = stack.pop()
|
||||
for inc in inverted.get(h, ()):
|
||||
if inc in source_files:
|
||||
if inc.stem == header.stem:
|
||||
preferred = inc
|
||||
break
|
||||
if preferred is None:
|
||||
preferred = inc
|
||||
if inc not in visited:
|
||||
visited.add(inc)
|
||||
stack.append(inc)
|
||||
if preferred is not None and preferred.stem == header.stem:
|
||||
break
|
||||
|
||||
if preferred is not None:
|
||||
result.add(preferred)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def resolve_files(
|
||||
input_files: list[str], build_dir: Path, repo_root: Path
|
||||
) -> list[str]:
|
||||
"""
|
||||
Split input into source files and headers. Source files are passed through;
|
||||
headers are resolved to the TUs that transitively include them.
|
||||
"""
|
||||
sources: list[Path] = []
|
||||
headers: list[Path] = []
|
||||
|
||||
for f in input_files:
|
||||
p = Path(f).resolve()
|
||||
if p.suffix in SOURCE_EXTENSIONS:
|
||||
sources.append(p)
|
||||
elif p.suffix in HEADER_EXTENSIONS:
|
||||
headers.append(p)
|
||||
|
||||
if not headers:
|
||||
return [str(p) for p in sources]
|
||||
|
||||
print(
|
||||
f"Resolving {len(headers)} header(s) to compilation units...", file=sys.stderr
|
||||
)
|
||||
inverted, source_files = build_include_graph(build_dir, repo_root)
|
||||
tus = find_tus_for_headers(headers, inverted, source_files)
|
||||
|
||||
if not tus:
|
||||
print(
|
||||
"Warning: no compilation units found that include the modified headers; "
|
||||
"skipping clang-tidy for headers.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
return sorted({str(p) for p in (*sources, *tus)})
|
||||
|
||||
|
||||
def staged_files(repo_root: Path) -> list[str]:
|
||||
result = subprocess.run(
|
||||
["git", "diff", "--staged", "--name-only", "--diff-filter=d"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=repo_root,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(
|
||||
"clang-tidy check failed: 'git diff --staged' command failed.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
if result.stderr:
|
||||
print(result.stderr, file=sys.stderr)
|
||||
sys.exit(result.returncode or 1)
|
||||
return [str(repo_root / p) for p in result.stdout.splitlines() if p]
|
||||
|
||||
|
||||
def main():
|
||||
if not os.environ.get("TIDY"):
|
||||
return 0
|
||||
|
||||
repo_root = Path(
|
||||
subprocess.check_output(
|
||||
["git", "rev-parse", "--show-toplevel"],
|
||||
cwd=Path(__file__).parent,
|
||||
text=True,
|
||||
).strip()
|
||||
)
|
||||
files = staged_files(repo_root)
|
||||
if not files:
|
||||
return 0
|
||||
|
||||
run_clang_tidy = find_run_clang_tidy()
|
||||
if not run_clang_tidy:
|
||||
print(
|
||||
"clang-tidy check failed: TIDY is enabled but neither "
|
||||
"'run-clang-tidy-21' nor 'run-clang-tidy' was found in PATH.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
build_dir = find_build_dir(repo_root)
|
||||
if not build_dir:
|
||||
print(
|
||||
"clang-tidy check failed: no build directory with compile_commands.json found "
|
||||
"(looked for .build/ and build/)",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
tidy_files = resolve_files(files, build_dir, repo_root)
|
||||
if not tidy_files:
|
||||
return 0
|
||||
|
||||
result = subprocess.run(
|
||||
[run_clang_tidy, "-quiet", "-p", str(build_dir), "-fix", "-allow-no-checks"]
|
||||
+ tidy_files
|
||||
)
|
||||
return result.returncode
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
37
bin/pre-commit/fix_include_style.py
Executable file
37
bin/pre-commit/fix_include_style.py
Executable file
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Converts quoted includes (#include "...") to angle-bracket includes
|
||||
(#include <...>), which is the required style in this project.
|
||||
|
||||
Usage: ./bin/pre-commit/fix_include_style.py <file1> <file2> ...
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
PATTERN = re.compile(r'^(\s*#include\s*)"([^"]+)"', re.MULTILINE)
|
||||
|
||||
|
||||
def fix_includes(path: Path) -> bool:
|
||||
original = path.read_text(encoding="utf-8")
|
||||
fixed = PATTERN.sub(r"\1<\2>", original)
|
||||
if fixed != original:
|
||||
path.write_text(fixed, encoding="utf-8")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def main() -> int:
|
||||
files = [Path(f) for f in sys.argv[1:]]
|
||||
success = True
|
||||
|
||||
for path in files:
|
||||
success &= fix_includes(path)
|
||||
|
||||
return 0 if success else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
34
bin/pre-commit/fix_pragma_once.py
Executable file
34
bin/pre-commit/fix_pragma_once.py
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Adds "#pragma once" to the top of header files that don't already have it.
|
||||
|
||||
Usage: ./bin/pre-commit/fix_pragma_once.py <file1> <file2> ...
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
PRAGMA_ONCE = "#pragma once\n\n"
|
||||
|
||||
|
||||
def fix_pragma_once(path: Path) -> bool:
|
||||
original = path.read_text(encoding="utf-8")
|
||||
if PRAGMA_ONCE not in original:
|
||||
path.write_text(PRAGMA_ONCE + original, encoding="utf-8")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def main() -> int:
|
||||
files = [Path(f) for f in sys.argv[1:]]
|
||||
success = True
|
||||
|
||||
for path in files:
|
||||
success &= fix_pragma_once(path)
|
||||
|
||||
return 0 if success else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -28,7 +28,7 @@
|
||||
# https://vl.ripple.com
|
||||
# https://unl.xrplf.org
|
||||
# http://127.0.0.1:8000
|
||||
# file:///etc/opt/ripple/vl.txt
|
||||
# file:///etc/xrpld/vl.txt
|
||||
#
|
||||
# [validator_list_keys]
|
||||
#
|
||||
@@ -43,11 +43,11 @@
|
||||
# ED307A760EE34F2D0CAA103377B1969117C38B8AA0AA1E2A24DAC1F32FC97087ED
|
||||
#
|
||||
|
||||
# The default validator list publishers that the rippled instance
|
||||
# The default validator list publishers that the xrpld instance
|
||||
# trusts.
|
||||
#
|
||||
# WARNING: Changing these values can cause your rippled instance to see a
|
||||
# validated ledger that contradicts other rippled instances'
|
||||
# WARNING: Changing these values can cause your xrpld instance to see a
|
||||
# validated ledger that contradicts other xrpld instances'
|
||||
# validated ledgers (aka a ledger fork) if your validator list(s)
|
||||
# do not sufficiently overlap with the list(s) used by others.
|
||||
# See: https://arxiv.org/pdf/1802.07242.pdf
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#
|
||||
# 2. Peer Protocol
|
||||
#
|
||||
# 3. Ripple Protocol
|
||||
# 3. XRPL protocol
|
||||
#
|
||||
# 4. HTTPS Client
|
||||
#
|
||||
@@ -383,7 +383,7 @@
|
||||
#
|
||||
# These settings control security and access attributes of the Peer to Peer
|
||||
# server section of the xrpld process. Peer Protocol implements the
|
||||
# Ripple Payment protocol. It is over peer connections that transactions
|
||||
# XRPL payment protocol. It is over peer connections that transactions
|
||||
# and validations are passed from to machine to machine, to determine the
|
||||
# contents of validated ledgers.
|
||||
#
|
||||
@@ -406,7 +406,7 @@
|
||||
#
|
||||
# [ips]
|
||||
#
|
||||
# List of hostnames or ips where the Ripple protocol is served. A default
|
||||
# List of hostnames or ips where the XRPL protocol is served. A default
|
||||
# starter list is included in the code and used if no other hostnames are
|
||||
# available.
|
||||
#
|
||||
@@ -435,7 +435,7 @@
|
||||
# List of IP addresses or hostnames to which xrpld should always attempt to
|
||||
# maintain peer connections with. This is useful for manually forming private
|
||||
# networks, for example to configure a validation server that connects to the
|
||||
# Ripple network through a public-facing server, or for building a set
|
||||
# XRPL network through a public-facing server, or for building a set
|
||||
# of cluster peers.
|
||||
#
|
||||
# One address or domain names per line is allowed. A port must be specified
|
||||
@@ -527,6 +527,17 @@
|
||||
#
|
||||
# The current default (which is subject to change) is 300 seconds.
|
||||
#
|
||||
# verify_endpoints = <0 | 1>
|
||||
#
|
||||
# If set to 0, the server will skip validation of endpoint
|
||||
# addresses received in TMEndpoints peer protocol messages,
|
||||
# allowing addresses that are not publicly routable or have a
|
||||
# port of 0. The default is 1 (verification enabled).
|
||||
#
|
||||
# WARNING: Disabling this option is a security risk and should
|
||||
# only be used for local testing and debugging. Do not disable
|
||||
# on mainnet.
|
||||
#
|
||||
#
|
||||
# [transaction_queue] EXPERIMENTAL
|
||||
#
|
||||
@@ -748,8 +759,8 @@
|
||||
# the folder in which the xrpld.cfg file is located.
|
||||
#
|
||||
# Examples:
|
||||
# /home/ripple/validators.txt
|
||||
# C:/home/ripple/validators.txt
|
||||
# /home/username/validators.txt
|
||||
# C:/home/username/validators.txt
|
||||
#
|
||||
# Example content:
|
||||
# [validators]
|
||||
@@ -840,7 +851,7 @@
|
||||
#
|
||||
# 0: Disable the ledger replay feature [default]
|
||||
# 1: Enable the ledger replay feature. With this feature enabled, when
|
||||
# acquiring a ledger from the network, a xrpld node only downloads
|
||||
# acquiring a ledger from the network, an xrpld node only downloads
|
||||
# the ledger header and the transactions instead of the whole ledger.
|
||||
# And the ledger is built by applying the transactions to the parent
|
||||
# ledger.
|
||||
@@ -853,7 +864,7 @@
|
||||
#
|
||||
# The xrpld server instance uses HTTPS GET requests in a variety of
|
||||
# circumstances, including but not limited to contacting trusted domains to
|
||||
# fetch information such as mapping an email address to a Ripple Payment
|
||||
# fetch information such as mapping an email address to an XRPL payment
|
||||
# Network address.
|
||||
#
|
||||
# [ssl_verify]
|
||||
@@ -942,6 +953,21 @@
|
||||
#
|
||||
# Optional keys for NuDB and RocksDB:
|
||||
#
|
||||
# cache_size Size of cache for database records. Default is 16384.
|
||||
# Setting this value to 0 will use the default value.
|
||||
#
|
||||
# cache_age Length of time in minutes to keep database records
|
||||
# cached. Default is 5 minutes. Setting this value to
|
||||
# 0 will use the default value.
|
||||
#
|
||||
# Note: if cache_size or cache_age is not specified,
|
||||
# default values will be used for the unspecified
|
||||
# parameter.
|
||||
#
|
||||
# Note: the cache will not be created if online_delete
|
||||
# is specified, because the rotating NodeStore does
|
||||
# not use this cache).
|
||||
#
|
||||
# fast_load Boolean. If set, load the last persisted ledger
|
||||
# from disk upon process start before syncing to
|
||||
# the network. This is likely to improve performance
|
||||
@@ -1227,7 +1253,7 @@
|
||||
#
|
||||
#----------
|
||||
#
|
||||
# The vote settings configure settings for the entire Ripple network.
|
||||
# The vote settings configure settings for the entire XRPL network.
|
||||
# While a single instance of xrpld cannot unilaterally enforce network-wide
|
||||
# settings, these choices become part of the instance's vote during the
|
||||
# consensus process for each voting ledger.
|
||||
@@ -1258,7 +1284,7 @@
|
||||
# default. Don't change this without understanding the consequences.
|
||||
#
|
||||
# Example:
|
||||
# account_reserve = 10000000 # 10 XRP
|
||||
# account_reserve = 1000000 # 1 XRP
|
||||
#
|
||||
# owner_reserve = <drops>
|
||||
#
|
||||
@@ -1270,7 +1296,7 @@
|
||||
# default. Don't change this without understanding the consequences.
|
||||
#
|
||||
# Example:
|
||||
# owner_reserve = 2000000 # 2 XRP
|
||||
# owner_reserve = 200000 # 0.2 XRP
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
@@ -1416,6 +1442,12 @@
|
||||
# in this section to a comma-separated list of the addresses
|
||||
# of your Clio servers, in order to bypass xrpld's rate limiting.
|
||||
#
|
||||
# TLS/SSL can be enabled for gRPC by specifying ssl_cert and ssl_key.
|
||||
# Both parameters must be provided together. The ssl_cert_chain parameter
|
||||
# is optional and provides intermediate CA certificates for the certificate
|
||||
# chain. The ssl_client_ca parameter is optional and enables mutual TLS
|
||||
# (client certificate verification).
|
||||
#
|
||||
# This port is commented out but can be enabled by removing
|
||||
# the '#' from each corresponding line including the entry under [server]
|
||||
#
|
||||
@@ -1449,10 +1481,7 @@ admin = 127.0.0.1
|
||||
protocol = http
|
||||
|
||||
[port_peer]
|
||||
# Many servers still use the legacy port of 51235, so for backward-compatibility
|
||||
# we maintain that port number here. However, for new servers we recommend
|
||||
# changing this to the default port of 2459.
|
||||
port = 51235
|
||||
port = 2459
|
||||
ip = 0.0.0.0
|
||||
# alternatively, to accept connections on IPv4 + IPv6, use:
|
||||
#ip = ::
|
||||
@@ -1465,11 +1494,74 @@ admin = 127.0.0.1
|
||||
protocol = ws
|
||||
send_queue_limit = 500
|
||||
|
||||
# gRPC TLS/SSL Configuration
|
||||
#
|
||||
# The gRPC port supports optional TLS/SSL encryption. When TLS is not
|
||||
# configured, the gRPC server will accept unencrypted connections.
|
||||
#
|
||||
# ssl_cert = <filename>
|
||||
# ssl_key = <filename>
|
||||
#
|
||||
# To enable TLS for gRPC, both ssl_cert and ssl_key must be specified.
|
||||
# If only one is provided, xrpld will fail to start.
|
||||
#
|
||||
# ssl_cert: Path to the server's SSL certificate file in PEM format.
|
||||
# ssl_key: Path to the server's SSL private key file in PEM format.
|
||||
#
|
||||
# When configured, the gRPC server will only accept TLS-encrypted
|
||||
# connections. Clients must use TLS (secure) channel credentials rather
|
||||
# than plaintext / insecure connections.
|
||||
#
|
||||
# ssl_cert_chain = <filename>
|
||||
#
|
||||
# Optional. Path to intermediate CA certificate(s) in PEM format that
|
||||
# complete the server's certificate chain.
|
||||
#
|
||||
# This file should contain the intermediate CA certificate(s) needed
|
||||
# to build a trust chain from the server certificate (ssl_cert) to a
|
||||
# root CA that clients trust. Multiple certificates should be
|
||||
# concatenated in PEM format.
|
||||
#
|
||||
# This is needed when your server certificate was signed by an
|
||||
# intermediate CA rather than directly by a root CA. Without this,
|
||||
# clients may fail to verify your server certificate.
|
||||
#
|
||||
# If not specified, only the server certificate from ssl_cert will be
|
||||
# presented to clients.
|
||||
#
|
||||
# ssl_client_ca = <filename>
|
||||
#
|
||||
# Optional. Path to a CA certificate file in PEM format for verifying
|
||||
# client certificates (mutual TLS / mTLS).
|
||||
#
|
||||
# When specified, the gRPC server will verify client certificates
|
||||
# against this CA. This enables mutual authentication where both the
|
||||
# server and client verify each other's identity.
|
||||
#
|
||||
# This is typically NOT needed for public-facing gRPC servers. Only
|
||||
# use this if you want to restrict access to clients with valid
|
||||
# certificates signed by the specified CA.
|
||||
#
|
||||
# If not specified, the server will use one-way TLS (server
|
||||
# authentication only) and will accept connections from any client.
|
||||
#
|
||||
[port_grpc]
|
||||
port = 50051
|
||||
ip = 127.0.0.1
|
||||
secure_gateway = 127.0.0.1
|
||||
|
||||
# Optional TLS/SSL configuration for gRPC
|
||||
# To enable TLS, uncomment and configure both ssl_cert and ssl_key:
|
||||
#ssl_cert = /etc/ssl/certs/grpc-server.crt
|
||||
#ssl_key = /etc/ssl/private/grpc-server.key
|
||||
|
||||
# Optional: Include intermediate CA certificates for complete certificate chain
|
||||
#ssl_cert_chain = /etc/ssl/certs/grpc-intermediate-ca.crt
|
||||
|
||||
# Optional: Enable mutual TLS (client certificate verification)
|
||||
# Uncomment to require and verify client certificates:
|
||||
#ssl_client_ca = /etc/ssl/certs/grpc-client-ca.crt
|
||||
|
||||
#[port_ws_public]
|
||||
#port = 6005
|
||||
#ip = 127.0.0.1
|
||||
@@ -1529,3 +1621,108 @@ validators.txt
|
||||
# set to ssl_verify to 0.
|
||||
[ssl_verify]
|
||||
1
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
# 11. Telemetry (OpenTelemetry Tracing)
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
# Enables distributed tracing via OpenTelemetry. Requires building with
|
||||
# -DXRPL_ENABLE_TELEMETRY=ON (telemetry Conan option).
|
||||
#
|
||||
# [telemetry]
|
||||
#
|
||||
# enabled=0
|
||||
#
|
||||
# Enable or disable telemetry at runtime. Default: 0 (disabled).
|
||||
#
|
||||
# service_name=xrpld
|
||||
#
|
||||
# OTel resource attribute `service.name`. Default: xrpld.
|
||||
# The node's network ID (from [network_id]) is automatically added
|
||||
# as the `xrpl.network.id` and `xrpl.network.type` resource attributes.
|
||||
#
|
||||
# service_instance_id=<node_public_key>
|
||||
#
|
||||
# OTel resource attribute `service.instance.id`. Uniquely identifies
|
||||
# this node. Default: the node's public key (auto-detected).
|
||||
#
|
||||
# endpoint=http://localhost:4318/v1/traces
|
||||
#
|
||||
# The OTLP/HTTP exporter endpoint. The server sends trace data as
|
||||
# protobuf-encoded HTTP POST requests to this URL.
|
||||
# Default: http://localhost:4318/v1/traces.
|
||||
#
|
||||
# --- TLS settings for the OTLP exporter connection ---
|
||||
#
|
||||
# use_tls=0
|
||||
#
|
||||
# Enable TLS for the OTLP/HTTP exporter connection. Default: 0 (off).
|
||||
#
|
||||
# tls_ca_cert=
|
||||
#
|
||||
# Path to a PEM-encoded CA certificate bundle for TLS verification.
|
||||
# Only used when use_tls=1. Default: empty (system CA store).
|
||||
#
|
||||
# tls_client_cert=
|
||||
#
|
||||
# Path to this node's PEM-encoded client certificate, presented to the
|
||||
# collector for mutual TLS (mTLS). Only used when use_tls=1. Leave empty
|
||||
# for one-way (server-only) TLS. Default: empty.
|
||||
#
|
||||
# tls_client_key=
|
||||
#
|
||||
# Path to the PEM-encoded private key for tls_client_cert. Required
|
||||
# whenever tls_client_cert is set. Only used when use_tls=1.
|
||||
# Default: empty.
|
||||
#
|
||||
# Head sampling is intentionally fixed at 1.0 (sample everything) and is
|
||||
# not configurable. A per-node sampling ratio would let nodes make
|
||||
# divergent keep/drop decisions for the same distributed trace, producing
|
||||
# broken/partial traces. A ParentBasedSampler ensures spans inheriting a
|
||||
# remote parent honor the upstream decision. Reduce volume at the collector
|
||||
# via tail sampling instead; for node-local post-hoc dropping use
|
||||
# SpanGuard::discard() in code.
|
||||
#
|
||||
# trace_rpc=1
|
||||
#
|
||||
# Enable tracing for JSON-RPC and WebSocket API request handling —
|
||||
# command parsing, execution, and response serialization. Default: 1.
|
||||
#
|
||||
# trace_transactions=1
|
||||
#
|
||||
# Enable tracing for the transaction lifecycle — submission, validation,
|
||||
# application to ledgers, and final disposition. Default: 1.
|
||||
#
|
||||
# trace_consensus=1
|
||||
#
|
||||
# Enable tracing for the consensus round lifecycle — proposals,
|
||||
# validations, mode changes, and ledger acceptance. Default: 1.
|
||||
#
|
||||
# trace_peer=1
|
||||
#
|
||||
# Enable tracing for peer-to-peer protocol messages — overlay message
|
||||
# send/receive, peer handshakes, and routing. High volume; enabled
|
||||
# by default. Default: 1.
|
||||
#
|
||||
# trace_ledger=1
|
||||
#
|
||||
# Enable tracing for ledger close and accept operations — ledger
|
||||
# building, state hashing, and write-back to the node store. Default: 1.
|
||||
#
|
||||
# --- Batch processor tuning ---
|
||||
#
|
||||
# batch_size=512
|
||||
#
|
||||
# Maximum number of spans exported in a single batch. Default: 512.
|
||||
#
|
||||
# batch_delay_ms=5000
|
||||
#
|
||||
# Maximum delay (milliseconds) before a partial batch is flushed.
|
||||
# Default: 5000 (5 seconds).
|
||||
#
|
||||
# max_queue_size=2048
|
||||
#
|
||||
# Maximum number of spans queued in memory before drops occur.
|
||||
# Default: 2048.
|
||||
#
|
||||
|
||||
@@ -56,3 +56,16 @@ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64|ARM64")
|
||||
else()
|
||||
message(FATAL_ERROR "Unknown architecture: ${CMAKE_SYSTEM_PROCESSOR}")
|
||||
endif()
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Sanitizers
|
||||
# --------------------------------------------------------------------
|
||||
# SANITIZERS is injected by the Conan toolchain when a sanitizer build is
|
||||
# requested (see conan/profiles/sanitizers). The flags are applied to the
|
||||
# 'common' target in XrplSanitizers; this flag lets other modules know a
|
||||
# sanitizer build is active without depending on that module.
|
||||
if(DEFINED SANITIZERS)
|
||||
set(SANITIZERS_ENABLED TRUE)
|
||||
else()
|
||||
set(SANITIZERS_ENABLED FALSE)
|
||||
endif()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user