Skip to content

Commit 72ed89c

Browse files
THLOgregorydemaylpahlavi
authored
chore: Creating a unified mechanism to generate blocklists for ckBTC and ckETH (dfinity#3401)
This PR adds a Python script to generate blocklists for ckBTC and ckETH based on the OFAC SDN list. There are some differences in the produced blocklist files compared to the current files: Differences to the [current ckBTC blocklist file](https://github.com/dfinity/ic/blob/master/rs/bitcoin/checker/src/blocklist.rs): 1. The new blocklist file has a comment at the beginning about the script that was used to generate it. 2. The new blocklist file has a more concise error message for the invalid Bitcoin address in the OFAC SDN list. Differences to the [current ckETH blocklist file](https://github.com/dfinity/ic/blob/master/rs/ethereum/cketh/minter/src/blocklist.rs): 1. The new blocklist file has a comment at the beginning about the script that was used to generate it, replacing the old comment. 2. Since Ethereum addresses are case-insensitive, all addresses are now consistently in lower case. 3. The parameter `from_address` in the function `is_blocked` has been changed to `address` to make it consistent with the corresponding function for ckBTC. Corresponding ticket: https://dfinity.atlassian.net/browse/XC-250 --------- Co-authored-by: gregorydemay <[email protected]> Co-authored-by: Louis Pahlavi <[email protected]> Co-authored-by: Grégory Demay <[email protected]>
1 parent 69988ae commit 72ed89c

File tree

1 file changed

+157
-0
lines changed

1 file changed

+157
-0
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import argparse
2+
import xml.etree.ElementTree as ET
3+
4+
# The following steps need to be carried out to update the blocklist for BTC or ETH.
5+
#
6+
# 1) Download the latest version of the OFAC SDN list from their website:
7+
# https://sanctionslist.ofac.treas.gov/Home/SdnList
8+
#
9+
# Specifically, download the file
10+
#
11+
# SDN_XML.ZIP
12+
#
13+
# and decompress it to retrieve the file 'SDN.XML'.
14+
#
15+
# 2) Run this script as follows:
16+
#
17+
# python generate_blocklist.py [currency (BTC or ETH)] [path to the SDN.XML file]
18+
#
19+
# The command will generate the file 'blocklist.rs' containing the retrieved addresses.
20+
#
21+
# 3) Override the current 'blocklist.rs' file with the newly generated file.
22+
23+
24+
# The ID type prefix for digital currencies.
25+
DIGITAL_CURRENCY_TYPE_PREFIX = "Digital Currency Address - "
26+
27+
# The blocked addresses are stored in this Rust file by default.
28+
DEFAULT_BLOCKLIST_FILENAME = "blocklist.rs"
29+
30+
# This prefix is needed for each element in the XML tree.
31+
PREFIX = "{https://sanctionslistservice.ofac.treas.gov/api/PublicationPreview/exports/XML}"
32+
33+
34+
# Handlers for different blocklists.
35+
class BitcoinBlocklistHandler:
36+
def preamble(self):
37+
return """#[cfg(test)]
38+
mod tests;
39+
40+
use bitcoin::Address;
41+
42+
/// The script to generate this file, including information about the source data, can be found here:
43+
/// /rs/cross-chain/scripts/generate_blocklist.py
44+
45+
/// BTC is not accepted from nor sent to addresses on this list.
46+
/// NOTE: Keep it sorted!
47+
pub const BTC_ADDRESS_BLOCKLIST: &[&str] = &[\n"""
48+
49+
def postamble(self):
50+
return """pub fn is_blocked(address: &Address) -> bool {
51+
BTC_ADDRESS_BLOCKLIST
52+
.binary_search(&address.to_string().as_ref())
53+
.is_ok()
54+
}"""
55+
56+
def format_address(self, address):
57+
return f'"{address}"'
58+
59+
def currency_symbol(self):
60+
return "XBT"
61+
62+
def sort(self, addresses):
63+
return sorted(addresses)
64+
65+
66+
class EthereumBlocklistHandler:
67+
def preamble(self):
68+
return """#[cfg(test)]
69+
mod tests;
70+
71+
use ic_ethereum_types::Address;
72+
73+
macro_rules! ethereum_address {
74+
($address:expr) => {
75+
Address::new(hex_literal::hex!($address))
76+
};
77+
}
78+
79+
/// The script to generate this file, including information about the source data, can be found here:
80+
/// /rs/cross-chain/scripts/generate_blocklist.py
81+
82+
/// ETH is not accepted from nor sent to addresses on this list.
83+
/// NOTE: Keep it sorted!
84+
const ETH_ADDRESS_BLOCKLIST: &[Address] = &[\n"""
85+
86+
def postamble(self):
87+
return """pub fn is_blocked(address: &Address) -> bool {
88+
ETH_ADDRESS_BLOCKLIST.binary_search(address).is_ok()
89+
}"""
90+
91+
def format_address(self, address):
92+
return f'ethereum_address!("{address[2:]}")'
93+
94+
def currency_symbol(self):
95+
return "ETH"
96+
97+
def sort(self, addresses):
98+
return sorted(addresses, key=lambda x: int(x[2:], 16))
99+
100+
101+
def extract_addresses(handler, xml_file_path):
102+
tree = ET.parse(xml_file_path)
103+
root = tree.getroot()
104+
105+
addresses = []
106+
107+
# Iterate over all ID elements.
108+
for id_item in root.findall(PREFIX + "sdnEntry/" + PREFIX + "idList" + "/" + PREFIX + "id"):
109+
# Put the ID components into a dictionary for simpler handling.
110+
id_dict = {}
111+
for sub_item in id_item:
112+
if sub_item.text.strip():
113+
id_dict[sub_item.tag] = sub_item.text
114+
115+
# Read the address, if any.
116+
if id_dict[PREFIX + "idType"] == DIGITAL_CURRENCY_TYPE_PREFIX + handler.currency_symbol():
117+
address = id_dict[PREFIX + "idNumber"]
118+
addresses.append(address)
119+
120+
# Remove duplicates.
121+
addresses = list(set(addresses))
122+
# Sort the addresses.
123+
addresses = handler.sort(addresses)
124+
return addresses
125+
126+
127+
def store_blocklist(blocklist_handler, addresses, filename):
128+
blocklist_file = open(filename, "w")
129+
blocklist_file.write(blocklist_handler.preamble())
130+
for address in addresses:
131+
blocklist_file.write(" " + blocklist_handler.format_address(address) + ",\n")
132+
print(address)
133+
blocklist_file.write("];\n\n")
134+
blocklist_file.write(blocklist_handler.postamble())
135+
blocklist_file.close()
136+
137+
138+
if __name__ == "__main__":
139+
parser = argparse.ArgumentParser()
140+
parser.add_argument("--currency", "-c", type=str, required=True, choices=["BTC", "ETH"], help="select the currency")
141+
parser.add_argument("--input", "-i", type=str, required=True, help="read the provided SDN.XML file")
142+
parser.add_argument(
143+
"--output", "-o", type=str, default=DEFAULT_BLOCKLIST_FILENAME, help="write the output to the provided path"
144+
)
145+
146+
args = parser.parse_args()
147+
148+
if args.currency == "BTC":
149+
blocklist_handler = BitcoinBlocklistHandler()
150+
else:
151+
blocklist_handler = EthereumBlocklistHandler()
152+
print("Extracting addresses from " + args.input + "...")
153+
addresses = extract_addresses(blocklist_handler, args.input)
154+
print("Done. Found " + str(len(addresses)) + " addresses.")
155+
print("Storing the addresses in the file " + args.output + "...")
156+
store_blocklist(blocklist_handler, addresses, args.output)
157+
print("Done.")

0 commit comments

Comments
 (0)