xrpld-lab 3.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: xrpld-lab
3
+ Version: 3.0.0
4
+ Summary: Build xrpld networks and standalone ledgers
5
+ Author: Denis Angell
6
+ Author-email: dangell@transia.co
7
+ Requires-Python: >=3.9.6,<4.0.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
15
+ Requires-Dist: requests (>=2.31.0,<3.0.0)
16
+ Requires-Dist: xrpld-publisher (>=2.0.0,<3.0.0)
@@ -0,0 +1,25 @@
1
+ [tool.poetry]
2
+ name = "xrpld-lab"
3
+ version = "3.0.0"
4
+ description = "Build xrpld networks and standalone ledgers"
5
+ authors = ["Denis Angell <dangell@transia.co>"]
6
+ packages = [
7
+ { include = "xrpld_lab" },
8
+ ]
9
+
10
+ [tool.poetry.dependencies]
11
+ python = "^3.9.6"
12
+ pyyaml = "^6.0.1"
13
+ requests = "^2.31.0"
14
+ xrpld-publisher = "^2.0.0"
15
+
16
+ [tool.poetry.dev-dependencies]
17
+ pytest = "^7.2.1"
18
+ flake8 = "^3.8.4"
19
+
20
+ [build-system]
21
+ requires = ["poetry-core>=1.0.0"]
22
+ build-backend = "poetry.core.masonry.api"
23
+
24
+ [tool.poetry.scripts]
25
+ xrpld-lab = "xrpld_lab.cli:main"
@@ -0,0 +1 @@
1
+ __version__ = "3.0.0"
@@ -0,0 +1,147 @@
1
+ """Amendment parsing and genesis-file helpers.
2
+
3
+ Parses C++ feature macro lines (XRPL_FEATURE, XRPL_FIX, REGISTER_FEATURE,
4
+ REGISTER_FIX) to extract amendment names and their SHA-512-half hashes,
5
+ then optionally writes them into a genesis JSON ledger.
6
+ """
7
+
8
+ import hashlib
9
+ import os
10
+ import re
11
+ from typing import Dict, List
12
+
13
+ from xrpld_lab.utils import read_json
14
+
15
+ _PACKAGE_DIR = os.path.abspath(os.path.dirname(__file__))
16
+
17
+
18
+ # ---------------------------------------------------------------------------
19
+ # Helpers
20
+ # ---------------------------------------------------------------------------
21
+
22
+
23
+ def _amendment_name_hash(name: str) -> str:
24
+ """SHA-512 half of a UTF-8 amendment name (first 64 hex chars, uppercase)."""
25
+ return hashlib.sha512(name.encode("utf-8")).digest().hex().upper()[:64]
26
+
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # Public API
30
+ # ---------------------------------------------------------------------------
31
+
32
+
33
+ def parse_supported(value: str) -> bool:
34
+ """Parse C++ Supported/DefaultVote value. ``'no'`` -> False, else True."""
35
+ return value != "no"
36
+
37
+
38
+ def get_feature_lines_from_path(path: str) -> list[str]:
39
+ """Read a C++ feature file and return its lines."""
40
+ with open(path, "r") as f:
41
+ return f.readlines()
42
+
43
+
44
+ def get_feature_lines_from_content(content: bytes) -> list[str]:
45
+ """Decode *bytes* content and split into lines."""
46
+ return content.decode("utf-8").splitlines()
47
+
48
+
49
+ def parse_amendments(lines: list) -> Dict[str, str]:
50
+ """Parse C++ macro lines into ``{amendment_name: sha512_half_hash}``.
51
+
52
+ Handles four macro styles:
53
+
54
+ * ``XRPL_FEATURE(Name, ...)``
55
+ * ``XRPL_FIX(Name, ...)`` -- prepends ``"fix"`` to the name
56
+ * ``REGISTER_FEATURE(Name, ...)``
57
+ * ``REGISTER_FIX(Name, ...)``
58
+
59
+ Only amendments marked ``Supported::yes`` (or any value other than ``no``)
60
+ are included. The hash is the first 64 hex characters of the SHA-512
61
+ digest of the UTF-8 encoded amendment name.
62
+ """
63
+ amendments: Dict[str, dict] = {}
64
+
65
+ for line in lines:
66
+ amendment_name: str = ""
67
+
68
+ if re.match(r"XRPL_FIX", line):
69
+ m = re.search(r"XRPL_FIX\)?.*?\((.*?),", line)
70
+ if not m:
71
+ continue
72
+ amendment_name = f"fix{m.group(1)}"
73
+ elif re.match(r"XRPL_FEATURE", line):
74
+ m = re.search(r"XRPL_FEATURE\((.*?),", line)
75
+ if not m:
76
+ continue
77
+ amendment_name = m.group(1)
78
+ elif re.match(r"REGISTER_FIX", line):
79
+ m = re.search(r"REGISTER_FIX\)?.*?\((.*?),", line)
80
+ if not m:
81
+ continue
82
+ amendment_name = m.group(1)
83
+ elif re.match(r"REGISTER_FEATURE", line):
84
+ m = re.search(r"REGISTER_FEATURE\((.*?),", line)
85
+ if not m:
86
+ continue
87
+ amendment_name = m.group(1)
88
+ else:
89
+ continue
90
+
91
+ supported_match = re.findall(r"Supported::(yes|no)", line)
92
+ default_vote_match = re.findall(r"DefaultVote::(yes|no)", line)
93
+
94
+ amendments[amendment_name] = {
95
+ "supported": parse_supported(
96
+ supported_match[0] if supported_match else "no"
97
+ ),
98
+ "default_vote": parse_supported(
99
+ default_vote_match[0] if default_vote_match else "no"
100
+ ),
101
+ }
102
+
103
+ return {
104
+ k: _amendment_name_hash(k)
105
+ for k, v in amendments.items()
106
+ if v["supported"] is True
107
+ }
108
+
109
+
110
+ def convert_to_list_of_hashes(features: Dict[str, str]) -> List[str]:
111
+ """Return a flat list of hash strings from *features*."""
112
+ return list(features.values())
113
+
114
+
115
+ def update_genesis(
116
+ features: Dict[str, str],
117
+ protocol_name: str,
118
+ genesis_path: str = None,
119
+ ) -> dict:
120
+ """Update a genesis JSON file with amendment hashes.
121
+
122
+ Parameters
123
+ ----------
124
+ features:
125
+ ``{name: hash}`` mapping produced by :func:`parse_amendments`.
126
+ protocol_name:
127
+ ``"xrpl"`` or ``"xahau"``.
128
+ genesis_path:
129
+ Explicit path to a genesis JSON file. When *None* the default
130
+ package location ``xrpld_lab/genesis.<protocol>.json`` is used.
131
+
132
+ Returns
133
+ -------
134
+ dict
135
+ The full genesis dict with the ``Amendments`` list replaced.
136
+ """
137
+ if genesis_path is None:
138
+ genesis_path = os.path.join(_PACKAGE_DIR, f"genesis.{protocol_name}.json")
139
+
140
+ json_dict = read_json(genesis_path)
141
+ new_amendments: List[str] = convert_to_list_of_hashes(features)
142
+
143
+ for entry in json_dict["ledger"]["accountState"]:
144
+ if "Amendments" in entry:
145
+ entry["Amendments"] = new_amendments
146
+
147
+ return json_dict