Quickstart

ENR Creation

You can create an ENR record as follows.

>>> from eth_keys import keys
>>> from eth_enr import UnsignedENR, ENR
>>> private_key = keys.PrivateKey(b'unicornsrainbowsunicornsrainbows')
>>> unsigned_enr = UnsignedENR(
... sequence_number=1,
... kv_pairs={
...     b'id': b'v4',
...     b'secp256k1': private_key.public_key.to_compressed_bytes(),
...     b'unicorns': b'rainbows',
... })
>>> enr = unsigned_enr.to_signed_enr(private_key.to_bytes())
>>> enr
enr:-Ie4QNRDUVEiOYTwwki59qs5SY_ofKSCbFL2BuslZ9fsZXGEMOlfxkFGpojFUj_ArnHMh4bv6E26frE1NII7z4xK9I0BgmlkgnY0iXNlY3AyNTZrMaEDvfDdonz3wUFd66sirz_3a0oRlsc9rlKp0SQeHEkcC6iIdW5pY29ybnOIcmFpbmJvd3M
>>> enr == ENR.from_repr("enr:-Ie4QNRDUVEiOYTwwki59qs5SY_ofKSCbFL2BuslZ9fsZXGEMOlfxkFGpojFUj_ArnHMh4bv6E26frE1NII7z4xK9I0BgmlkgnY0iXNlY3AyNTZrMaEDvfDdonz3wUFd66sirz_3a0oRlsc9rlKp0SQeHEkcC6iIdW5pY29ybnOIcmFpbmJvd3M")  # recover an ENR from it's text representation
True

Storing ENR records

You can use the eth_enr.ENRDB to store ENR records. The underlying storage is flexible and accepts any dictionary-like object.

>>> from eth_keys import keys
>>> from eth_enr import UnsignedENR, ENRDB
>>> private_key = keys.PrivateKey(b'unicornsrainbowsunicornsrainbows')
>>> unsigned_enr = UnsignedENR(
... sequence_number=1,
... kv_pairs={
...     b'id': b'v4',
...     b'secp256k1': private_key.public_key.to_compressed_bytes(),
... })
>>> enr = unsigned_enr.to_signed_enr(private_key.to_bytes())
>>> enr_db = ENRDB({})
>>> enr_db.get_enr(enr.node_id)  # not yet in database
Traceback (most recent call last):
  File "/home/piper/.pyenv/versions/3.6.9/lib/python3.6/doctest.py", line 1330, in __run
    compileflags, 1), test.globs)
  File "<doctest default[6]>", line 1, in <module>
    enr_db.get_enr(enr.node_id)  # not yet in database
  File "/home/piper/projects/eth-enr/eth_enr/enr_db.py", line 57, in get_enr
    return rlp.decode(self.db[self._get_enr_key(node_id)], sedes=ENR)  # type: ignore
KeyError: b'l?\x85b\xc8\x03\xbf\xae5\xa8\xf5K\x85\x82\xa2\x89V\xb9%\x93M\x03\xdd\xb4Xu\xe1\x8e\x85\x93\x12\xc1:enr'
>>> enr_db.set_enr(enr)
>>> enr_db.get_enr(enr.node_id)
enr:-HW4QDBN_uzB2BgXNgpjCN83hSE13oI46ZtFOmWnmYkGTZWrfRF6Yk60HcoiyuLDXqCTcj8fqk2DWetU2ZYJrXUEylIBgmlkgnY0iXNlY3AyNTZrMaEDvfDdonz3wUFd66sirz_3a0oRlsc9rlKp0SQeHEkcC6g
>>> updated_enr = UnsignedENR(
... sequence_number=2,
... kv_pairs={
...     b'id': b'v4',
...     b'secp256k1': private_key.public_key.to_compressed_bytes(),
... }).to_signed_enr(private_key.to_bytes())
>>> enr_db.set_enr(updated_enr)
>>> enr_db.set_enr(enr, raise_on_error=True)  # throws exception due to old sequence number
Traceback (most recent call last):
  File "/home/piper/.pyenv/versions/3.6.9/lib/python3.6/doctest.py", line 1330, in __run
    compileflags, 1), test.globs)
  File "<doctest default[11]>", line 1, in <module>
    enr_db.set_enr(enr)  # throws exception due to old sequence number
  File "/home/piper/projects/eth-enr/eth_enr/enr_db.py", line 51, in set_enr
    f"Cannot overwrite existing ENR ({existing_enr.sequence_number}) with old one "
eth_enr.exceptions.OldSequenceNumber: Cannot overwrite existing ENR (2) with old one (1)
>>> assert enr_db.get_enr(updated_enr.node_id) == updated_enr

Using the ENRManager

The eth_enr.ENRMAnager automates creation, updating, and storage of ENR records.

>>> from eth_keys import keys
>>> from eth_enr import ENRManager, ENRDB
>>> private_key = keys.PrivateKey(b'unicornsrainbowsunicornsrainbows')
>>> manager = ENRManager(private_key, ENRDB({}))
>>> manager.enr
enr:-HW4QDBN_uzB2BgXNgpjCN83hSE13oI46ZtFOmWnmYkGTZWrfRF6Yk60HcoiyuLDXqCTcj8fqk2DWetU2ZYJrXUEylIBgmlkgnY0iXNlY3AyNTZrMaEDvfDdonz3wUFd66sirz_3a0oRlsc9rlKp0SQeHEkcC6g
>>> manager.enr.sequence_number
1
>>> manager.update((b'foo', b'bar'))
>>> manager.enr
enr:-H24QNUv1DBIpMITIUjJN8s7foWBJ33rR0liWCu4nVDaXk7ACcXpiMiFJHPC8UKTNkXfN3DXGwPX-Q6KL1uMZwNeyGMCg2Zvb4NiYXKCaWSCdjSJc2VjcDI1NmsxoQO98N2ifPfBQV3rqyKvP_drShGWxz2uUqnRJB4cSRwLqA
>>> manager.enr[b'foo']
b'bar'
>>> manager.enr.sequence_number
2
>>> manager.update((b'foo', None))  # `None` triggers removal of a key.
>>> manager.enr
enr:-HW4QFeb9Qg_RNSWamKytj4Eh2eICVKSauQfp4PMY45YQdGzAyFnLjZBU-IuktiGKGiEz2nbEo6w4qNOu_D2Xdmr08gDgmlkgnY0iXNlY3AyNTZrMaEDvfDdonz3wUFd66sirz_3a0oRlsc9rlKp0SQeHEkcC6g
>>> manager.enr[b'foo']
Traceback (most recent call last):
  File "/home/piper/.pyenv/versions/3.6.9/lib/python3.6/doctest.py", line 1330, in __run
    compileflags, 1), test.globs)
  File "<doctest default[10]>", line 1, in <module>
    manager.enr[b'foo']
  File "/home/piper/projects/eth-enr/eth_enr/enr.py", line 93, in __getitem__
    return self._kv_pairs[key]
KeyError: b'foo'

Querying ENR Records

You can use the eth_enr.QueryableENRDB which exposes the same API as eth_enr.ENRDB with one additional eth_enr.QueryableENRDB.query() method.

The eth_enr.QueryableENRDB operates on top of any SQLite3 database using the sqlite3 standard library.

>>> import sqlite3
>>> from eth_keys import keys
>>> from eth_enr import UnsignedENR, QueryableENRDB
>>> from eth_enr.constraints import KeyExists
>>> private_key_a = keys.PrivateKey(b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA')
>>> private_key_b = keys.PrivateKey(b'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB')
>>> private_key_c = keys.PrivateKey(b'CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC')
>>> enr_a = UnsignedENR(
... sequence_number=1,
... kv_pairs={
...     b'id': b'v4',
...     b'secp256k1': private_key_a.public_key.to_compressed_bytes(),
...     b'unicorns': b'rainbows',
... }).to_signed_enr(private_key_a.to_bytes())
>>> enr_b = UnsignedENR(
... sequence_number=7,
... kv_pairs={
...     b'id': b'v4',
...     b'secp256k1': private_key_b.public_key.to_compressed_bytes(),
...     b'unicorns': b'rainbows',
...     b'cupcakes': b'sparkles',
... }).to_signed_enr(private_key_b.to_bytes())
>>> enr_c = UnsignedENR(
... sequence_number=2,
... kv_pairs={
...     b'id': b'v4',
...     b'secp256k1': private_key_c.public_key.to_compressed_bytes(),
... }).to_signed_enr(private_key_c.to_bytes())
>>> connection = sqlite3.connect(":memory:")
>>> enr_db = QueryableENRDB(connection)
>>> enr_db.set_enr(enr_a)
>>> enr_db.set_enr(enr_b)
>>> enrs_with_unicorns = tuple(enr_db.query(KeyExists(b'unicorns')))
>>> assert enr_a in enrs_with_unicorns
>>> assert enr_b in enrs_with_unicorns
>>> assert enr_c not in enrs_with_unicorns
>>> enrs_with_cupcakes = tuple(enr_db.query(KeyExists(b'cupcakes')))
>>> assert enr_a not in enrs_with_cupcakes
>>> assert enr_b in enrs_with_cupcakes
>>> assert enr_c not in enrs_with_cupcakes