|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471 |
- ###############################################################################
- #
- # The MIT License (MIT)
- #
- # Copyright (c) typedef int GmbH
- #
- # Permission is hereby granted, free of charge, to any person obtaining a copy
- # of this software and associated documentation files (the "Software"), to deal
- # in the Software without restriction, including without limitation the rights
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- # copies of the Software, and to permit persons to whom the Software is
- # furnished to do so, subject to the following conditions:
- #
- # The above copyright notice and this permission notice shall be included in
- # all copies or substantial portions of the Software.
- #
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- # THE SOFTWARE.
- #
- ###############################################################################
-
- import os
- from binascii import b2a_hex
- from typing import Optional, Dict, Any, List
-
- import web3
- from web3.contract import Contract
- from ens import ENS
-
- from twisted.internet.defer import Deferred, inlineCallbacks
- from twisted.internet.threads import deferToThread
-
- from autobahn.wamp.interfaces import ICryptosignKey, IEthereumKey
- from autobahn.wamp.message import identify_realm_name_category
- from autobahn.xbr import make_w3, EIP712AuthorityCertificate
-
-
- class Seeder(object):
- """
-
- """
- __slots__ = (
- '_frealm',
- '_operator',
- '_label',
- '_country',
- '_legal',
- '_endpoint',
- '_bandwidth_requested',
- '_bandwidth_offered',
- )
-
- def __init__(self,
- frealm: 'FederatedRealm',
- operator: Optional[str] = None,
- label: Optional[str] = None,
- country: Optional[str] = None,
- legal: Optional[str] = None,
- endpoint: Optional[str] = None,
- bandwidth_requested: Optional[int] = None,
- bandwidth_offered: Optional[int] = None,
- ):
- """
-
- :param frealm:
- :param operator:
- :param label:
- :param country:
- :param legal:
- :param endpoint:
- :param bandwidth_requested:
- :param bandwidth_offered:
- """
- self._frealm: FederatedRealm = frealm
- self._operator: Optional[str] = operator
- self._label: Optional[str] = label
- self._country: Optional[str] = country
- self._legal: Optional[str] = legal
- self._endpoint: Optional[str] = endpoint
- self._bandwidth_requested: Optional[str] = bandwidth_requested
- self._bandwidth_offered: Optional[str] = bandwidth_offered
-
- @staticmethod
- def _create_eip712_connect(chain_id: int,
- verifying_contract: bytes,
- channel_binding: str,
- channel_id: bytes,
- block_no: int,
- challenge: bytes,
- pubkey: bytes,
- realm: bytes,
- delegate: bytes,
- seeder: bytes,
- bandwidth: int):
- channel_binding = channel_binding or ''
- channel_id = channel_id or b''
- assert chain_id
- assert verifying_contract
- assert channel_binding is not None
- assert channel_id is not None
- assert block_no
- assert challenge
- assert pubkey
- assert realm
- assert delegate
- assert bandwidth
-
- data = {
- 'types': {
- 'EIP712Domain': [
- {
- 'name': 'name',
- 'type': 'string'
- },
- {
- 'name': 'version',
- 'type': 'string'
- },
- ],
- 'EIP712SeederConnect': [
- {
- 'name': 'chainId',
- 'type': 'uint256'
- },
- {
- 'name': 'verifyingContract',
- 'type': 'address'
- },
- {
- 'name': 'channel_binding',
- 'type': 'string'
- },
- {
- 'name': 'channel_id',
- 'type': 'bytes32'
- },
- {
- 'name': 'block_no',
- 'type': 'uint256'
- },
- {
- 'name': 'challenge',
- 'type': 'bytes32'
- },
- {
- 'name': 'pubkey',
- 'type': 'bytes32'
- },
- {
- 'name': 'realm',
- 'type': 'address'
- },
- {
- 'name': 'delegate',
- 'type': 'address'
- },
- {
- 'name': 'seeder',
- 'type': 'address'
- },
- {
- 'name': 'bandwidth',
- 'type': 'uint32'
- },
- ]
- },
- 'primaryType': 'EIP712SeederConnect',
- 'domain': {
- 'name': 'XBR',
- 'version': '1',
- },
- 'message': {
- 'chainId': chain_id,
- 'verifyingContract': verifying_contract,
- 'channel_binding': channel_binding,
- 'channel_id': channel_id,
- 'block_no': block_no,
- 'challenge': challenge,
- 'pubkey': pubkey,
- 'realm': realm,
- 'delegate': delegate,
- 'seeder': seeder,
- 'bandwidth': bandwidth,
- }
- }
-
- return data
-
- @inlineCallbacks
- def create_authextra(self,
- client_key: ICryptosignKey,
- delegate_key: IEthereumKey,
- bandwidth_requested: int,
- channel_id: Optional[bytes] = None,
- channel_binding: Optional[str] = None) -> Deferred:
- """
-
- :param client_key:
- :param delegate_key:
- :param bandwidth_requested:
- :param channel_id:
- :param channel_binding:
- :return:
- """
- chain_id = 1
- # FIXME
- verifying_contract = b'\x01' * 20
- block_no = 1
- challenge = os.urandom(32)
- eip712_data = Seeder._create_eip712_connect(chain_id=chain_id,
- verifying_contract=verifying_contract,
- channel_binding=channel_binding,
- channel_id=channel_id,
- block_no=block_no,
- challenge=challenge,
- pubkey=client_key.public_key(binary=True),
- # FIXME
- # realm=self._frealm.address(binary=True),
- realm=b'\x02' * 20,
- delegate=delegate_key.address(binary=False),
- # FIXME
- # seeder=self._operator,
- seeder=b'\x03' * 20,
- bandwidth=bandwidth_requested)
- signature = yield delegate_key.sign_typed_data(eip712_data)
- authextra = {
- # string
- 'pubkey': client_key.public_key(binary=False),
-
- # string
- 'challenge': challenge,
-
- # string
- 'channel_binding': channel_binding,
-
- # string
- 'channel_id': channel_id,
-
- # address
- # FIXME
- 'realm': '7f' * 20,
-
- # int
- 'chain_id': chain_id,
-
- # int
- 'block_no': block_no,
-
- # address
- 'delegate': delegate_key.address(binary=False),
-
- # address
- 'seeder': self._operator,
-
- # int: requested bandwidth in kbps
- 'bandwidth': bandwidth_requested,
-
- # string: Eth signature by delegate_key over EIP712 typed data as above
- 'signature': b2a_hex(signature).decode(),
- }
- return authextra
-
- @property
- def frealm(self) -> 'FederatedRealm':
- """
-
- :return:
- """
- return self._frealm
-
- @property
- def operator(self) -> Optional[str]:
- """
- Operator address, e.g. ``"0xe59C7418403CF1D973485B36660728a5f4A8fF9c"``.
-
- :return: The Ethereum address of the endpoint operator.
- """
- return self._operator
-
- @property
- def label(self) -> Optional[str]:
- """
- Operator endpoint label.
-
- :return: A human readable label for the operator or specific operator endpoint.
- """
- return self._label
-
- @property
- def country(self) -> Optional[str]:
- """
- Operator country (ISO 3166-1 alpha-2), e.g. ``"US"``.
-
- :return: ISO 3166-1 alpha-2 country code.
- """
- return self._country
-
- @property
- def legal(self) -> Optional[str]:
- """
-
- :return:
- """
- return self._legal
-
- @property
- def endpoint(self) -> Optional[str]:
- """
- Public WAMP endpoint of seeder. Secure WebSocket URL resolving to a public IPv4
- or IPv6 listening url accepting incoming WAMP-WebSocket connections,
- e.g. ``wss://service1.example.com/ws``.
-
- :return: The endpoint URL.
- """
- return self._endpoint
-
- @property
- def bandwidth_requested(self) -> Optional[int]:
- """
-
- :return:
- """
- return self._bandwidth_requested
-
- @property
- def bandwidth_offered(self) -> Optional[int]:
- """
-
- :return:
- """
- return self._bandwidth_offered
-
-
- class FederatedRealm(object):
- """
- A federated realm is a WAMP application realm with a trust anchor rooted in Ethereum, and
- which can be shared between multiple parties.
-
- A federated realm is globally identified on an Ethereum chain (e.g. on Mainnet or Rinkeby)
- by an Ethereum address associated to a federated realm owner by an on-chain record stored
- in the WAMP Network contract. The federated realm address thus only needs to exist as an
- identifier of the federated realm-owner record.
- """
- __slots__ = (
- '_name_or_address',
- '_gateway_config',
- '_status',
- '_name_category',
- '_w3',
- '_ens',
- '_address',
- '_contract',
-
- '_seeders',
- '_root_ca',
- '_catalog',
- '_meta',
- )
- # FIXME
- CONTRACT_ADDRESS = web3.Web3.toChecksumAddress('0xF7acf1C4CB4a9550B8969576573C2688B48988C2')
- CONTRACT_ABI: str = ''
-
- def __init__(self, name_or_address: str, gateway_config: Optional[Dict[str, Any]] = None):
- """
- Instantiate a federated realm from a federated realm ENS name (which is resolved to an Ethereum
- address) or Ethereum address.
-
- :param name_or_address: Ethereum ENS name or address.
- :param gateway_config: If provided, use this Ethereum gateway. If not provided,
- connect via Infura to Ethereum Mainnet, which requires an environment variable
- ``WEB3_INFURA_PROJECT_ID`` with your Infura project ID.
- """
- self._name_or_address = name_or_address
- self._gateway_config = gateway_config
-
- # status, will change to 'RUNNING' after initialize() has completed
- self._status = 'STOPPED'
-
- self._name_category: Optional[str] = identify_realm_name_category(self._name_or_address)
- if self._name_category not in ['eth', 'ens', 'reverse_ens']:
- raise ValueError('name_or_address "{}" not an Ethereum address or ENS name'.format(self._name_or_address))
-
- # will be filled once initialize()'ed
- self._w3 = None
- self._ens = None
-
- # address identifying the federated realm
- self._address: Optional[str] = None
-
- # will be initialized with a FederatedRealm contract instance
- self._contract: Optional[Contract] = None
-
- # cache of federated realm seeders, filled once in status running
- self._seeders: List[Seeder] = []
-
- self._root_ca = None
-
- @property
- def status(self) -> str:
- return self._status
-
- @property
- def name_or_address(self) -> str:
- return self._name_or_address
-
- @property
- def gateway_config(self) -> Optional[Dict[str, Any]]:
- return self._gateway_config
-
- @property
- def name_category(self) -> Optional[str]:
- return self._name_category
-
- @property
- def address(self):
- return self._address
-
- def root_ca(self) -> EIP712AuthorityCertificate:
- assert self._status == 'RUNNING'
- return self._root_ca
-
- @property
- def seeders(self) -> List[Seeder]:
- return self._seeders
-
- def initialize(self):
- """
-
- :return:
- """
- if self._status != 'STOPPED':
- raise RuntimeError('cannot start in status "{}"'.format(self._status))
- d = deferToThread(self._initialize_background)
- return d
-
- def _initialize_background(self):
- self._status = 'STARTING'
-
- if self._gateway_config:
- self._w3 = make_w3(self._gateway_config)
- else:
- raise RuntimeError('cannot auto-configure ethereum connection (was removed from web3.py in v6)')
- # https://github.com/ethereum/web3.py/issues/1416
- # https://github.com/ethereum/web3.py/pull/2706
- # from web3.auto.infura import w3
- # self._w3 = w3
- self._ens = ENS.from_web3(self._w3)
-
- if self._name_category in ['ens', 'reverse_ens']:
- if self._name_category == 'reverse_ens':
- name = ''.join(reversed(self._name_or_address.split('.')))
- else:
- name = self._name_or_address
- self._address = self._ens.address(name)
- elif self._name_category == 'eth':
- self._address = self._w3.toChecksumAddress(self._name_or_address)
- else:
- assert False, 'should not arrive here'
-
- # https://web3py.readthedocs.io/en/stable/contracts.html#web3.contract.Contract
- # https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.contract
- # self._contract = self._w3.eth.contract(address=self.CONTRACT_ADDRESS, abi=self.CONTRACT_ABI)
-
- # FIXME: get IPFS hash, download file, unzip seeders
-
- self._status = 'RUNNING'
|