Skip to content

Commit 02ddf5c

Browse files
dvmazurmryab
andauthored
Add multiaddr as subpackage (#637)
* Add multiaddr as subpackage * Remove py-multiaddr dependency * Move to hivemind.utils, add copyright notices * Add multiaddr tests * Add newline in requirements.txt --------- Co-authored-by: Max Ryabinin <mryabinin0@gmail.com>
1 parent 767afa5 commit 02ddf5c

32 files changed

+2304
-20
lines changed

hivemind/dht/dht.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,12 @@
77
from functools import partial
88
from typing import Awaitable, Callable, Iterable, List, Optional, Sequence, TypeVar, Union
99

10-
from multiaddr import Multiaddr
11-
1210
from hivemind.dht.node import DEFAULT_NUM_WORKERS, DHTNode
1311
from hivemind.dht.routing import DHTKey, DHTValue, Subkey
1412
from hivemind.dht.validation import CompositeValidator, RecordValidatorBase
1513
from hivemind.p2p import P2P, PeerID
1614
from hivemind.utils import MPFuture, get_logger, switch_to_uvloop
15+
from hivemind.utils.multiaddr import Multiaddr
1716
from hivemind.utils.timed_storage import DHTExpiration, ValueWithExpiration
1817

1918
logger = get_logger(__name__)

hivemind/dht/node.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
Union,
2424
)
2525

26-
from multiaddr import Multiaddr
2726
from sortedcontainers import SortedSet
2827

2928
from hivemind.dht.crypto import DHTRecord, RecordValidatorBase
@@ -34,6 +33,7 @@
3433
from hivemind.p2p import P2P, PeerID
3534
from hivemind.utils import MSGPackSerializer, SerializerBase, get_logger
3635
from hivemind.utils.auth import AuthorizerBase
36+
from hivemind.utils.multiaddr import Multiaddr
3737
from hivemind.utils.timed_storage import DHTExpiration, TimedStorage, ValueWithExpiration
3838

3939
logger = get_logger(__name__)

hivemind/p2p/p2p_daemon.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from typing import Any, AsyncIterator, Awaitable, Callable, List, Optional, Sequence, Tuple, Type, TypeVar, Union
1313

1414
from google.protobuf.message import Message
15-
from multiaddr import Multiaddr
1615

1716
import hivemind.hivemind_cli as cli
1817
import hivemind.p2p.p2p_daemon_bindings.p2pclient as p2pclient
@@ -24,6 +23,7 @@
2423
from hivemind.utils.asyncio import as_aiter, asingle
2524
from hivemind.utils.crypto import RSAPrivateKey
2625
from hivemind.utils.logging import get_logger, golog_level_to_python, loglevel, python_level_to_golog
26+
from hivemind.utils.multiaddr import Multiaddr
2727

2828
logger = get_logger(__name__)
2929

hivemind/p2p/p2p_daemon_bindings/control.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
from typing import AsyncIterator, Awaitable, Callable, Dict, Iterable, Optional, Sequence, Tuple
1010
from uuid import UUID, uuid4
1111

12-
from multiaddr import Multiaddr, protocols
13-
1412
from hivemind.p2p.p2p_daemon_bindings.datastructures import PeerID, PeerInfo, StreamInfo
1513
from hivemind.p2p.p2p_daemon_bindings.utils import (
1614
DispatchFailure,
@@ -22,6 +20,7 @@
2220
)
2321
from hivemind.proto import p2pd_pb2 as p2pd_pb
2422
from hivemind.utils.logging import get_logger
23+
from hivemind.utils.multiaddr import Multiaddr, protocols
2524

2625
StreamHandler = Callable[[StreamInfo, asyncio.StreamReader, asyncio.StreamWriter], Awaitable[None]]
2726

hivemind/p2p/p2p_daemon_bindings/datastructures.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
import base58
1111
import multihash
1212
from cryptography.hazmat.primitives import serialization
13-
from multiaddr import Multiaddr
1413

1514
from hivemind.proto import crypto_pb2, p2pd_pb2
15+
from hivemind.utils.multiaddr import Multiaddr
1616

1717

1818
class PeerID:

hivemind/p2p/p2p_daemon_bindings/p2pclient.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
from contextlib import asynccontextmanager
99
from typing import AsyncIterator, Iterable, Sequence, Tuple
1010

11-
from multiaddr import Multiaddr
12-
1311
from hivemind.p2p.p2p_daemon_bindings.control import (
1412
DEFAULT_MAX_MSG_SIZE,
1513
ControlClient,
@@ -18,6 +16,7 @@
1816
TUnaryHandler,
1917
)
2018
from hivemind.p2p.p2p_daemon_bindings.datastructures import PeerID, PeerInfo, StreamInfo
19+
from hivemind.utils.multiaddr import Multiaddr
2120

2221

2322
class Client:
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# This code is originally taken from https://github.com/multiformats/py-multiaddr
2+
#
3+
# The MIT License (MIT)
4+
#
5+
# Copyright (c) 2014-2015 Steven Buss
6+
# Copyright (c) 2019-2020 Alexander Schlarb
7+
#
8+
# Permission is hereby granted, free of charge, to any person obtaining a copy
9+
# of this software and associated documentation files (the "Software"), to deal
10+
# in the Software without restriction, including without limitation the rights
11+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
# copies of the Software, and to permit persons to whom the Software is
13+
# furnished to do so, subject to the following conditions:
14+
#
15+
# The above copyright notice and this permission notice shall be included in all
16+
# copies or substantial portions of the Software.
17+
#
18+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+
# SOFTWARE.
25+
from .multiaddr import Multiaddr # NOQA
26+
27+
__author__ = "Steven Buss"
28+
__email__ = "steven.buss@gmail.com"
29+
__version__ = "0.0.9"
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# This code is originally taken from https://github.com/multiformats/py-multiaddr
2+
#
3+
# The MIT License (MIT)
4+
#
5+
# Copyright (c) 2014-2015 Steven Buss
6+
# Copyright (c) 2019-2020 Alexander Schlarb
7+
#
8+
# Permission is hereby granted, free of charge, to any person obtaining a copy
9+
# of this software and associated documentation files (the "Software"), to deal
10+
# in the Software without restriction, including without limitation the rights
11+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
# copies of the Software, and to permit persons to whom the Software is
13+
# furnished to do so, subject to the following conditions:
14+
#
15+
# The above copyright notice and this permission notice shall be included in all
16+
# copies or substantial portions of the Software.
17+
#
18+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+
# SOFTWARE.
25+
import importlib
26+
27+
# These are special sizes
28+
LENGTH_PREFIXED_VAR_SIZE = -1
29+
30+
31+
class NoneCodec:
32+
SIZE = 0
33+
IS_PATH = False
34+
35+
36+
CODEC_CACHE = {}
37+
38+
39+
def codec_by_name(name):
40+
if name is None: # Special “do nothing – expect nothing” pseudo-codec
41+
return NoneCodec
42+
codec = CODEC_CACHE.get(name)
43+
if not codec:
44+
codec = CODEC_CACHE[name] = importlib.import_module(".{0}".format(name), __name__)
45+
return codec
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# This code is originally taken from https://github.com/multiformats/py-multiaddr
2+
#
3+
# The MIT License (MIT)
4+
#
5+
# Copyright (c) 2014-2015 Steven Buss
6+
# Copyright (c) 2019-2020 Alexander Schlarb
7+
#
8+
# Permission is hereby granted, free of charge, to any person obtaining a copy
9+
# of this software and associated documentation files (the "Software"), to deal
10+
# in the Software without restriction, including without limitation the rights
11+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
# copies of the Software, and to permit persons to whom the Software is
13+
# furnished to do so, subject to the following conditions:
14+
#
15+
# The above copyright notice and this permission notice shall be included in all
16+
# copies or substantial portions of the Software.
17+
#
18+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+
# SOFTWARE.
25+
import base58
26+
import cid
27+
28+
from . import LENGTH_PREFIXED_VAR_SIZE
29+
30+
SIZE = LENGTH_PREFIXED_VAR_SIZE
31+
IS_PATH = False
32+
33+
34+
# Spec: https://github.com/libp2p/specs/blob/master/peer-ids/peer-ids.md#string-representation
35+
CIDv0_PREFIX_TO_LENGTH = {
36+
# base58btc prefixes for valid lengths 1 – 42 with the identity “hash” function
37+
"12": [5, 12, 19, 23, 30, 41, 52, 56],
38+
"13": [9, 16, 34, 45],
39+
"14": [27, 38, 49, 60],
40+
"15": [3, 6, 20],
41+
"16": [3, 6, 13, 20, 31, 42, 53],
42+
"17": [3, 13, 42],
43+
"18": [3],
44+
"19": [3, 24, 57],
45+
"1A": [24, 35, 46],
46+
"1B": [35],
47+
"1D": [17],
48+
"1E": [10, 17],
49+
"1F": [10],
50+
"1G": [10, 28, 50],
51+
"1H": [28, 39],
52+
"1P": [21],
53+
"1Q": [21],
54+
"1R": [21, 54],
55+
"1S": [54],
56+
"1T": [7, 32, 43],
57+
"1U": [7, 32, 43],
58+
"1V": [7],
59+
"1W": [7, 14],
60+
"1X": [7, 14],
61+
"1Y": [7, 14],
62+
"1Z": [7, 14],
63+
"1f": [4],
64+
"1g": [4, 58],
65+
"1h": [4, 25, 58],
66+
"1i": [4, 25],
67+
"1j": [4, 25],
68+
"1k": [4, 25, 47],
69+
"1m": [4, 36, 47],
70+
"1n": [4, 36],
71+
"1o": [4, 36],
72+
"1p": [4],
73+
"1q": [4],
74+
"1r": [4],
75+
"1s": [4],
76+
"1t": [4],
77+
"1u": [4],
78+
"1v": [4],
79+
"1w": [4],
80+
"1x": [4],
81+
"1y": [4],
82+
"1z": [4, 18],
83+
# base58btc prefix for length 42 with the sha256 hash function
84+
"Qm": [46],
85+
}
86+
87+
PROTO_NAME_TO_CIDv1_CODEC = {
88+
# The “p2p” multiaddr protocol requires all keys to use the “libp2p-key” multicodec
89+
"p2p": "libp2p-key",
90+
}
91+
92+
93+
def to_bytes(proto, string):
94+
expected_codec = PROTO_NAME_TO_CIDv1_CODEC.get(proto.name)
95+
96+
if len(string) in CIDv0_PREFIX_TO_LENGTH.get(string[0:2], ()): # CIDv0
97+
# Upgrade the wire (binary) representation of any received CIDv0 string
98+
# to CIDv1 if we can determine which multicodec value to use
99+
if expected_codec:
100+
return cid.make_cid(1, expected_codec, base58.b58decode(string)).buffer
101+
102+
return base58.b58decode(string)
103+
else: # CIDv1+
104+
parsed = cid.from_string(string)
105+
106+
# Ensure CID has correct codec for protocol
107+
if expected_codec and parsed.codec != expected_codec:
108+
raise ValueError("“{0}” multiaddr CIDs must use the “{1}” multicodec".format(proto.name, expected_codec))
109+
110+
return parsed.buffer
111+
112+
113+
def _is_binary_cidv0_multihash(buf):
114+
if buf.startswith(b"\x12\x20") and len(buf) == 34: # SHA2-256
115+
return True
116+
117+
if (buf[0] == 0x00 and buf[1] in range(43)) and len(buf) == (buf[1] + 2): # Identity hash
118+
return True
119+
120+
return False
121+
122+
123+
def to_string(proto, buf):
124+
expected_codec = PROTO_NAME_TO_CIDv1_CODEC.get(proto.name)
125+
126+
if _is_binary_cidv0_multihash(buf): # CIDv0
127+
if not expected_codec:
128+
# Simply encode as base58btc as there is nothing better to do
129+
return base58.b58encode(buf).decode("ascii")
130+
131+
# “Implementations SHOULD display peer IDs using the first (raw
132+
# base58btc encoded multihash) format until the second format is
133+
# widely supported.”
134+
#
135+
# In the future the following line should instead convert the multihash
136+
# to CIDv1 and with the `expected_codec` and wrap it in base32:
137+
# return cid.make_cid(1, expected_codec, buf).encode("base32").decode("ascii")
138+
return base58.b58encode(buf).decode("ascii")
139+
else: # CIDv1+
140+
parsed = cid.from_bytes(buf)
141+
142+
# Ensure CID has correct codec for protocol
143+
if expected_codec and parsed.codec != expected_codec:
144+
raise ValueError("“{0}” multiaddr CIDs must use the “{1}” multicodec".format(proto.name, expected_codec))
145+
146+
# “Implementations SHOULD display peer IDs using the first (raw
147+
# base58btc encoded multihash) format until the second format is
148+
# widely supported.”
149+
if expected_codec and _is_binary_cidv0_multihash(parsed.multihash):
150+
return base58.b58encode(parsed.multihash).decode("ascii")
151+
152+
return parsed.encode("base32").decode("ascii")
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# This code is originally taken from https://github.com/multiformats/py-multiaddr
2+
#
3+
# The MIT License (MIT)
4+
#
5+
# Copyright (c) 2014-2015 Steven Buss
6+
# Copyright (c) 2019-2020 Alexander Schlarb
7+
#
8+
# Permission is hereby granted, free of charge, to any person obtaining a copy
9+
# of this software and associated documentation files (the "Software"), to deal
10+
# in the Software without restriction, including without limitation the rights
11+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
# copies of the Software, and to permit persons to whom the Software is
13+
# furnished to do so, subject to the following conditions:
14+
#
15+
# The above copyright notice and this permission notice shall be included in all
16+
# copies or substantial portions of the Software.
17+
#
18+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+
# SOFTWARE.
25+
import idna
26+
27+
from . import LENGTH_PREFIXED_VAR_SIZE
28+
29+
SIZE = LENGTH_PREFIXED_VAR_SIZE
30+
IS_PATH = False
31+
32+
33+
def to_bytes(proto, string):
34+
return idna.uts46_remap(string).encode("utf-8")
35+
36+
37+
def to_string(proto, buf):
38+
string = buf.decode("utf-8")
39+
for label in string.split("."):
40+
idna.check_label(label)
41+
return string

0 commit comments

Comments
 (0)