diff --git a/Makefile b/Makefile index bdc33b498..fd8a3c595 100755 --- a/Makefile +++ b/Makefile @@ -73,6 +73,16 @@ docs: spelling: cd docs && sphinx-build -b spelling . _spelling +run_docs: + twistd --nodaemon web --port=tcp:8090 --path=./docs/build/html/ + +test_xbr_cli: + xbrnetwork + xbrnetwork version + xbrnetwork get-member + xbrnetwork get-market --market=1388ddf6-fe36-4201-b1aa-cb7e36b4cfb3 + xbrnetwork get-actor + xbrnetwork get-actor --market=1388ddf6-fe36-4201-b1aa-cb7e36b4cfb3 test_mnemonic: # python -m pytest -rsx autobahn/xbr/test/test_mnemonic.py diff --git a/autobahn/_version.py b/autobahn/_version.py index d4ba1b63a..e20d25b7a 100644 --- a/autobahn/_version.py +++ b/autobahn/_version.py @@ -24,4 +24,4 @@ # ############################################################################### -__version__ = '20.4.3' +__version__ = '20.5.1.dev1' diff --git a/autobahn/wamp/serializer.py b/autobahn/wamp/serializer.py index 4e89016eb..1c1136327 100644 --- a/autobahn/wamp/serializer.py +++ b/autobahn/wamp/serializer.py @@ -311,7 +311,7 @@ def unserialize(self, payload, isBinary=None): self._unserialized_rated_messages += int(math.ceil(float(len(payload)) / self.RATED_MESSAGE_SIZE)) # maybe auto-reset and trigger user callback .. - if self._autoreset_callback and ((self._autoreset_duration and (time_ns() - self._stats_reset) >= self._autoreset_duration) or(self._autoreset_rated_messages and self.stats_rated_messages() >= self._autoreset_rated_messages)): + if self._autoreset_callback and ((self._autoreset_duration and (time_ns() - self._stats_reset) >= self._autoreset_duration) or (self._autoreset_rated_messages and self.stats_rated_messages() >= self._autoreset_rated_messages)): stats = self.stats(reset=True) self._autoreset_callback(stats) diff --git a/autobahn/xbr/__init__.py b/autobahn/xbr/__init__.py index 7bf0aab41..f93a6da57 100644 --- a/autobahn/xbr/__init__.py +++ b/autobahn/xbr/__init__.py @@ -35,6 +35,7 @@ from autobahn.xbr._abi import XBR_TOKEN_ABI, XBR_NETWORK_ABI, XBR_MARKET_ABI, XBR_CATALOG_ABI, XBR_CHANNEL_ABI # noqa from autobahn.xbr._abi import XBR_DEBUG_TOKEN_ADDR, XBR_DEBUG_NETWORK_ADDR, XBR_DEBUG_MARKET_ADDR, XBR_DEBUG_CATALOG_ADDR, XBR_DEBUG_CHANNEL_ADDR # noqa + from autobahn.xbr._abi import XBR_DEBUG_TOKEN_ADDR_SRC, XBR_DEBUG_NETWORK_ADDR_SRC, XBR_DEBUG_MARKET_ADDR_SRC, XBR_DEBUG_CATALOG_ADDR_SRC, XBR_DEBUG_CHANNEL_ADDR_SRC # noqa from autobahn.xbr._interfaces import IMarketMaker, IProvider, IConsumer, ISeller, IBuyer # noqa from autobahn.xbr._util import make_w3, pack_uint256, unpack_uint256 # noqa diff --git a/autobahn/xbr/_abi.py b/autobahn/xbr/_abi.py index 309e5ae29..ec5b3a1ce 100644 --- a/autobahn/xbr/_abi.py +++ b/autobahn/xbr/_abi.py @@ -35,13 +35,11 @@ # print('Using eth_hash backend {}'.format(keccak256)) import web3 -XBR_TOKEN_FN = pkg_resources.resource_filename('autobahn', 'xbr/contracts/XBRToken.json') -XBR_NETWORK_FN = pkg_resources.resource_filename('autobahn', 'xbr/contracts/XBRNetwork.json') -XBR_MARKET_FN = pkg_resources.resource_filename('autobahn', 'xbr/contracts/XBRMarket.json') -XBR_CATALOG_FN = pkg_resources.resource_filename('autobahn', 'xbr/contracts/XBRCatalog.json') -XBR_CHANNEL_FN = pkg_resources.resource_filename('autobahn', 'xbr/contracts/XBRChannel.json') - +# +# Set default XBR contract addresses to +# XBR v20.4.2 @ Rinkeby (https://github.com/crossbario/xbr-protocol/issues/106) +# if 'XBR_DEBUG_TOKEN_ADDR' in os.environ: _token_adr = os.environ['XBR_DEBUG_TOKEN_ADDR'] try: @@ -50,9 +48,10 @@ except Exception as e: raise RuntimeError('could not parse Ethereum address for XBR_DEBUG_TOKEN_ADDR={} - {}'.format(_token_adr, e)) XBR_DEBUG_TOKEN_ADDR = _token_adr + XBR_DEBUG_TOKEN_ADDR_SRC = 'env' else: - XBR_DEBUG_TOKEN_ADDR = '0x0000000000000000000000000000000000000000' - print('WARNING: The XBR smart contracts are not yet deployed to public networks. Please set XBR_DEBUG_TOKEN_ADDR manually.') + XBR_DEBUG_TOKEN_ADDR = '0x8d41eF64D49eA1550B4b41a8959D856601441503' + XBR_DEBUG_TOKEN_ADDR_SRC = 'builtin' if 'XBR_DEBUG_NETWORK_ADDR' in os.environ: _netw_adr = os.environ['XBR_DEBUG_NETWORK_ADDR'] @@ -62,9 +61,10 @@ except Exception as e: raise RuntimeError('could not parse Ethereum address for XBR_DEBUG_NETWORK_ADDR={} - {}'.format(_netw_adr, e)) XBR_DEBUG_NETWORK_ADDR = _netw_adr + XBR_DEBUG_NETWORK_ADDR_SRC = 'env' else: - XBR_DEBUG_NETWORK_ADDR = '0x0000000000000000000000000000000000000000' - print('WARNING: The XBR smart contracts are not yet deployed to public networks. Please set XBR_DEBUG_NETWORK_ADDR manually.') + XBR_DEBUG_NETWORK_ADDR = '0xBfB616f885D581328FC6c3ad53481231Cc9b1bcf' + XBR_DEBUG_NETWORK_ADDR_SRC = 'builtin' if 'XBR_DEBUG_MARKET_ADDR' in os.environ: _mrkt_adr = os.environ['XBR_DEBUG_MARKET_ADDR'] @@ -74,9 +74,10 @@ except Exception as e: raise RuntimeError('could not parse Ethereum address for XBR_DEBUG_MARKET_ADDR={} - {}'.format(_mrkt_adr, e)) XBR_DEBUG_MARKET_ADDR = _mrkt_adr + XBR_DEBUG_MARKET_ADDR_SRC = 'env' else: - XBR_DEBUG_MARKET_ADDR = '0x0000000000000000000000000000000000000000' - print('WARNING: The XBR smart contracts are not yet deployed to public networks. Please set XBR_DEBUG_MARKET_ADDR manually.') + XBR_DEBUG_MARKET_ADDR = '0x27d4E6534134d9B1b5E2190cf8Ea170C8D05fb66' + XBR_DEBUG_MARKET_ADDR_SRC = 'builtin' if 'XBR_DEBUG_CATALOG_ADDR' in os.environ: _ctlg_adr = os.environ['XBR_DEBUG_CATALOG_ADDR'] @@ -86,9 +87,10 @@ except Exception as e: raise RuntimeError('could not parse Ethereum address for XBR_DEBUG_CATALOG_ADDR={} - {}'.format(_ctlg_adr, e)) XBR_DEBUG_CATALOG_ADDR = _ctlg_adr + XBR_DEBUG_CATALOG_ADDR_SRC = 'env' else: - XBR_DEBUG_CATALOG_ADDR = '0x0000000000000000000000000000000000000000' - print('WARNING: The XBR smart contracts are not yet deployed to public networks. Please set XBR_DEBUG_CATALOG_ADDR manually.') + XBR_DEBUG_CATALOG_ADDR = '0x96284C34bD2A805589F9673F2534ED691672cAa0' + XBR_DEBUG_CATALOG_ADDR_SRC = 'builtin' if 'XBR_DEBUG_CHANNEL_ADDR' in os.environ: _chnl_adr = os.environ['XBR_DEBUG_CHANNEL_ADDR'] @@ -98,19 +100,32 @@ except Exception as e: raise RuntimeError('could not parse Ethereum address for XBR_DEBUG_CHANNEL_ADDR={} - {}'.format(_chnl_adr, e)) XBR_DEBUG_CHANNEL_ADDR = _chnl_adr + XBR_DEBUG_CHANNEL_ADDR_SRC = 'env' else: - XBR_DEBUG_CHANNEL_ADDR = '0x0000000000000000000000000000000000000000' - print('WARNING: The XBR smart contracts are not yet deployed to public networks. Please set XBR_DEBUG_CHANNEL_ADDR manually.') + XBR_DEBUG_CHANNEL_ADDR = '0xA20C8bA0e86606cCBEE14A50acA0604Ce667F508' + XBR_DEBUG_CHANNEL_ADDR_SRC = 'builtin' def _load_json(contract_name): fn = pkg_resources.resource_filename('autobahn', 'xbr/contracts/{}.json'.format(contract_name)) - # fn = os.path.join(os.path.dirname(__file__), '../build/contracts/{}.json'.format(contract_name)) with open(fn) as f: data = json.loads(f.read()) return data +# +# XBR contract ABI file names +# +XBR_TOKEN_FN = pkg_resources.resource_filename('autobahn', 'xbr/contracts/XBRToken.json') +XBR_NETWORK_FN = pkg_resources.resource_filename('autobahn', 'xbr/contracts/XBRNetwork.json') +XBR_MARKET_FN = pkg_resources.resource_filename('autobahn', 'xbr/contracts/XBRMarket.json') +XBR_CATALOG_FN = pkg_resources.resource_filename('autobahn', 'xbr/contracts/XBRCatalog.json') +XBR_CHANNEL_FN = pkg_resources.resource_filename('autobahn', 'xbr/contracts/XBRChannel.json') + + +# +# XBR contract ABIs +# XBR_TOKEN_ABI = _load_json('XBRToken')['abi'] XBR_NETWORK_ABI = _load_json('XBRNetwork')['abi'] XBR_MARKET_ABI = _load_json('XBRMarket')['abi'] diff --git a/autobahn/xbr/_cli.py b/autobahn/xbr/_cli.py index 3c5f0d8d7..ff16d6391 100644 --- a/autobahn/xbr/_cli.py +++ b/autobahn/xbr/_cli.py @@ -26,12 +26,19 @@ import sys from autobahn import xbr +from autobahn import __version__ if not xbr.HAS_XBR: print("\nYou must install the [xbr] extra to use xbrnetwork") print("For example, \"pip install autobahn[xbr]\".") sys.exit(1) +from autobahn.xbr._abi import XBR_DEBUG_TOKEN_ADDR, XBR_DEBUG_NETWORK_ADDR, XBR_DEBUG_MARKET_ADDR, \ + XBR_DEBUG_CATALOG_ADDR, XBR_DEBUG_CHANNEL_ADDR + +from autobahn.xbr._abi import XBR_DEBUG_TOKEN_ADDR_SRC, XBR_DEBUG_NETWORK_ADDR_SRC, XBR_DEBUG_MARKET_ADDR_SRC, \ + XBR_DEBUG_CATALOG_ADDR_SRC, XBR_DEBUG_CHANNEL_ADDR_SRC + import uuid import binascii import argparse @@ -44,11 +51,13 @@ import multihash import cbor2 -import txaio +import numpy as np +import txaio txaio.use_twisted() from twisted.internet import reactor +from twisted.internet.threads import deferToThread from twisted.internet.error import ReactorNotRunning from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner @@ -60,15 +69,11 @@ from autobahn.xbr import sign_eip712_member_register, sign_eip712_market_create, sign_eip712_market_join from autobahn.xbr import ActorType, ChannelType +from autobahn.xbr._config import load_or_create_profile +from autobahn.xbr._util import hlval, hlid, hltype -_INFURA_CONFIG = { - "type": "infura", - "network": "rinkeby", - "key": "40************************", - "secret": "55*************************" -} -_COMMANDS = ['get-member', 'register-member', 'register-member-verify', +_COMMANDS = ['version', 'get-member', 'register-member', 'register-member-verify', 'get-market', 'create-market', 'create-market-verify', 'get-actor', 'join-market', 'join-market-verify', 'get-channel', 'open-channel', 'close-channel'] @@ -77,24 +82,33 @@ class Client(ApplicationSession): def __init__(self, config=None): - self.log.info('{klass}.__init__(config={config})', klass=self.__class__.__name__, config=config) - ApplicationSession.__init__(self, config) # FIXME self._default_gas = 100000 + self._chain_id = 4 + + profile = config.extra['profile'] + + if 'ethkey' in config.extra and config.extra['ethkey']: + self._ethkey_raw = config.extra['ethkey'] + else: + self._ethkey_raw = profile.ethkey - self._ethkey_raw = config.extra['ethkey'] self._ethkey = eth_keys.keys.PrivateKey(self._ethkey_raw) self._ethadr = web3.Web3.toChecksumAddress(self._ethkey.public_key.to_canonical_address()) self._ethadr_raw = binascii.a2b_hex(self._ethadr[2:]) - self.log.info("Client (delegate) Ethereum key loaded (adr=0x{adr})", - adr=self._ethadr) + self.log.info('Client Ethereum key loaded, public address is {adr}', + func=hltype(self.__init__), adr=hlid(self._ethadr)) - self._key = cryptosign.SigningKey.from_key_bytes(config.extra['cskey']) - self.log.info("Client (delegate) WAMP-cryptosign authentication key loaded (pubkey=0x{pubkey})", - pubkey=self._key.public_key()) + if 'cskey' in config.extra and config.extra['cskey']: + cskey = config.extra['cskey'] + else: + cskey = profile.cskey + self._key = cryptosign.SigningKey.from_key_bytes(cskey) + self.log.info('Client WAMP authentication key loaded, public key is {pubkey}', + func=hltype(self.__init__), pubkey=hlid('0x' + self._key.public_key())) self._running = True @@ -103,8 +117,6 @@ def onUserError(self, fail, msg): self.leave('wamp.error', msg) def onConnect(self): - self.log.info('{klass}.onConnect()', klass=self.__class__.__name__) - if self.config.realm == 'xbrnetwork': authextra = { 'pubkey': self._key.public_key(), @@ -112,13 +124,15 @@ def onConnect(self): 'challenge': None, 'channel_binding': 'tls-unique' } + self.log.info('Client connected, now joining realm "{realm}" with WAMP-cryptosign authentication ..', + realm=hlid(self.config.realm)) self.join(self.config.realm, authmethods=['cryptosign'], authextra=authextra) else: + self.log.info('Client connected, now joining realm "{realm}" (no authentication) ..', + realm=hlid(self.config.realm)) self.join(self.config.realm) def onChallenge(self, challenge): - self.log.info('{klass}.onChallenge(challenge={challenge})', klass=self.__class__.__name__, challenge=challenge) - if challenge.method == 'cryptosign': signed_challenge = self._key.sign_challenge(self, challenge) return signed_challenge @@ -126,7 +140,12 @@ def onChallenge(self, challenge): raise RuntimeError('unable to process authentication method {}'.format(challenge.method)) async def onJoin(self, details): - self.log.info('{klass}.onJoin(details={details})', klass=self.__class__.__name__, details=details) + self.log.info('Ok, client joined on realm "{realm}" [session={session}, authid="{authid}", authrole="{authrole}"]', + realm=hlid(details.realm), + session=hlid(details.session), + authid=hlid(details.authid), + authrole=hlid(details.authrole), + details=details) try: if details.realm == 'xbrnetwork': await self._do_xbrnetwork_realm(details) @@ -139,22 +158,16 @@ async def onJoin(self, details): self.leave() def onLeave(self, details): - self.log.info('{klass}.onLeave(details={details})', klass=self.__class__.__name__, details=details) - + self.log.info('Client left realm (reason="{reason}")', reason=hlval(details.reason)) self._running = False if details.reason == 'wamp.close.normal': - self.log.info('Shutting down ..') # user initiated leave => end the program self.config.runner.stop() self.disconnect() - else: - # continue running the program (let ApplicationRunner perform auto-reconnect attempts ..) - self.log.info('Will continue to run (reconnect)!') def onDisconnect(self): - self.log.info('{klass}.onDisconnect()', klass=self.__class__.__name__) - + self.log.info('Client disconnected') try: reactor.stop() except ReactorNotRunning: @@ -181,8 +194,8 @@ async def _do_xbrnetwork_realm(self, details): else: # WAMP authid on xbrnetwork follows this format: "member-" member_oid = uuid.UUID(details.authid[7:]) - member_data = await self.call('network.xbr.console.get_member', member_oid.bytes) - self.log.info('already a member in the XBR network:\n\n{member_data}\n', member_data=pformat(member_data)) + # member_data = await self.call('network.xbr.console.get_member', member_oid.bytes) + # self.log.info('Address is already a member in the XBR network:\n\n{member_data}', member_data=pformat(member_data)) assert command in ['get-member', 'get-market', 'create-market', 'create-market-verify', 'get-actor', 'join-market', 'join-market-verify'] @@ -230,7 +243,16 @@ async def _do_xbrnetwork_realm(self, details): assert False, 'should not arrive here' async def _do_market_realm(self, details): - self._w3 = make_w3(_INFURA_CONFIG) + profile = self.config.extra['profile'] + + blockchain_gateway = { + "type": "infura", + "network": profile.infura_network, + "key": profile.infura_key, + "secret": profile.infura_secret + } + + self._w3 = make_w3(blockchain_gateway) xbr.setProvider(self._w3) command = self.config.extra['command'] @@ -246,11 +268,22 @@ async def _do_market_realm(self, details): else: assert False, 'should not arrive here' elif command == 'open-channel': + # market in which to open the new buyer/seller (payment/paying) channel market_oid = self.config.extra['market'] - channel_oid = self.config.extra['channel'] + + # read UUID of the new channel to be created from command line OR auto-generate a new one + channel_oid = self.config.extra['channel'] or uuid.uuid4() + + # buyer/seller (payment/paying) channel channel_type = self.config.extra['channel_type'] + + # the delgate allowed to use the channel delegate = self.config.extra['delegate'] + + # amount of market coins for initial channel balance amount = self.config.extra['amount'] + + # now open the channel .. await self._do_open_channel(market_oid, channel_oid, channel_type, delegate, amount) else: assert False, 'should not arrive here' @@ -259,13 +292,29 @@ async def _do_get_member(self, member_adr): is_member = await self.call('network.xbr.console.is_member', member_adr) if is_member: member_data = await self.call('network.xbr.console.get_member_by_wallet', member_adr) - member_adr = web3.Web3.toChecksumAddress(member_data['address']) + + member_data['address'] = web3.Web3.toChecksumAddress(member_data['address']) + member_data['oid'] = uuid.UUID(bytes=member_data['oid']) + member_data['balance']['eth'] = web3.Web3.fromWei(unpack_uint256(member_data['balance']['eth']), 'ether') + member_data['balance']['xbr'] = web3.Web3.fromWei(unpack_uint256(member_data['balance']['xbr']), 'ether') + member_data['created'] = np.datetime64(member_data['created'], 'ns') + member_level = member_data['level'] - member_balance_eth = int(unpack_uint256(member_data['balance']['eth']) / 10 ** 18) - member_balance_xbr = int(unpack_uint256(member_data['balance']['xbr']) / 10 ** 18) - self.log.info('Found member with address {member_adr}, member level {member_level}: {member_balance_eth} ETH, {member_balance_xbr} XBR', - member_adr=member_adr, member_level=member_level, member_balance_eth=member_balance_eth, - member_balance_xbr=member_balance_xbr) + MEMBER_LEVEL_TO_STR = { + # Member is active. + 1: 'ACTIVE', + # Member is active and verified. + 2: 'VERIFIED', + # Member is retired. + 3: 'RETIRED', + # Member is subject to a temporary penalty. + 4: 'PENALTY', + # Member is currently blocked and cannot current actively participate in the market. + 5: 'BLOCKED', + } + member_data['level'] = MEMBER_LEVEL_TO_STR.get(member_level, None) + + self.log.info('Member found:\n\n{member_data}\n', member_data=pformat(member_data)) else: self.log.warn('Address 0x{member_adr} is not a member in the XBR network', member_adr=binascii.b2a_hex(member_adr).decode()) @@ -274,29 +323,50 @@ async def _do_get_actor(self, market_oid, actor_adr): is_member = await self.call('network.xbr.console.is_member', actor_adr) if is_member: actor = await self.call('network.xbr.console.get_member_by_wallet', actor_adr) - actor_oid = actor['oid'] + actor_oid = uuid.UUID(bytes=actor['oid']) actor_adr = web3.Web3.toChecksumAddress(actor['address']) actor_level = actor['level'] - actor_balance_eth = int(unpack_uint256(actor['balance']['eth']) / 10 ** 18) - actor_balance_xbr = int(unpack_uint256(actor['balance']['xbr']) / 10 ** 18) - self.log.info('Found member with address {member_adr}, member level {member_level}: {member_balance_eth} ETH, {member_balance_xbr} XBR', - member_adr=actor_adr, member_level=actor_level, member_balance_eth=actor_balance_eth, - member_balance_xbr=actor_balance_xbr) + actor_balance_eth = web3.Web3.fromWei(unpack_uint256(actor['balance']['eth']), 'ether') + actor_balance_xbr = web3.Web3.fromWei(unpack_uint256(actor['balance']['xbr']), 'ether') + self.log.info('Found member with address {member_adr} (member level {member_level}, balances: {member_balance_eth} ETH, {member_balance_xbr} XBR)', + member_adr=hlid(actor_adr), + member_level=hlval(actor_level), + member_balance_eth=hlval(actor_balance_eth), + member_balance_xbr=hlval(actor_balance_xbr)) if market_oid: - # FIXME - raise NotImplementedError() + market_oids = [market_oid.bytes] else: - market_oids = await self.call('network.xbr.console.get_markets_by_actor', actor_oid) - if market_oids: - self.log.info('Member is actor in {cnt_markets} markets!', cnt_markets=len(market_oids)) - for market_oid in market_oids: - market = await self.call('network.xbr.console.get_market', market_oid) - self.log.info('Actor is joined to market {market_oid} (market owner 0x{owner_oid})', - market_oid=uuid.UUID(bytes=market_oid), - owner_oid=binascii.b2a_hex(market['owner']).decode()) - else: - self.log.info('Member is not yet actor in any market!') + market_oids = await self.call('network.xbr.console.get_markets_by_actor', actor_oid.bytes) + + if market_oids: + for market_oid in market_oids: + # market = await self.call('network.xbr.console.get_market', market_oid) + result = await self.call('network.xbr.console.get_actor_in_market', market_oid, actor['address']) + for actor in result: + actor['actor'] = web3.Web3.toChecksumAddress(actor['actor']) + actor['timestamp'] = np.datetime64(actor['timestamp'], 'ns') + actor['joined'] = unpack_uint256(actor['joined']) if actor['joined'] else None + actor['market'] = uuid.UUID(bytes=actor['market']) + actor['security'] = web3.Web3.fromWei(unpack_uint256(actor['security']), 'ether') if actor['security'] else None + actor['signature'] = '0x' + binascii.b2a_hex(actor['signature']).decode() if actor['signature'] else None + actor['tid'] = '0x' + binascii.b2a_hex(actor['tid']).decode() if actor['tid'] else None + + actor_type = actor['actor_type'] + ACTOR_TYPE_TO_STR = { + # Actor is a XBR Provider. + 1: 'PROVIDER', + # Actor is a XBR Consumer. + 2: 'CONSUMER', + # Actor is both a XBR Provider and XBR Consumer. + 3: 'PROVIDER_CONSUMER', + } + actor['actor_type'] = ACTOR_TYPE_TO_STR.get(actor_type, None) + + self.log.info('Actor is joined to market {market_oid}:\n\n{actor}\n', + market_oid=hlid(uuid.UUID(bytes=market_oid)), actor=pformat(actor)) + else: + self.log.info('Member is not yet actor in any market!') else: self.log.warn('Address 0x{member_adr} is not a member in the XBR network', member_adr=binascii.b2a_hex(actor_adr).decode()) @@ -526,11 +596,18 @@ async def _do_get_market(self, member_oid, market_oid): if market['owner'] == member_adr: self.log.info('You are market owner (operator)!') else: - self.log.info('Marked is owned by {owner}', owner=market['owner']) + self.log.info('Marked is owned by {owner}', owner=hlid(web3.Web3.toChecksumAddress(market['owner']))) + + market['market'] = uuid.UUID(bytes=market['market']) + market['owner'] = web3.Web3.toChecksumAddress(market['owner']) + market['maker'] = web3.Web3.toChecksumAddress(market['maker']) + market['coin'] = web3.Web3.toChecksumAddress(market['coin']) + market['timestamp'] = np.datetime64(market['timestamp'], 'ns') - self.log.info('Market information:\n{market}', market=pformat(market)) + self.log.info('Market {market_oid} information:\n\n{market}\n', + market_oid=hlid(market_oid), market=pformat(market)) else: - self.log.warn('No market {market_oid} found', market_oid) + self.log.warn('No market {market_oid} found!', market_oid=hlid(market_oid)) async def _do_join_market(self, member_oid, market_oid, actor_type): @@ -601,8 +678,8 @@ async def _do_open_channel(self, market_oid, channel_oid, channel_type, delegate member_adr = self._ethkey.public_key.to_canonical_address() config = await self.call('xbr.marketmaker.get_config') - marketmaker = config['marketmaker'] - recipient = config['owner'] + marketmaker = binascii.a2b_hex(config['marketmaker'][2:]) + recipient = binascii.a2b_hex(config['owner'][2:]) verifying_chain_id = config['verifying_chain_id'] verifying_contract_adr = binascii.a2b_hex(config['verifying_contract_adr'][2:]) @@ -611,19 +688,27 @@ async def _do_open_channel(self, market_oid, channel_oid, channel_type, delegate if amount > 0: if channel_type == ChannelType.PAYMENT: - allowance1 = xbr.xbrtoken.functions.allowance(member_adr, xbr.xbrchannel.address).call() - xbr.xbrtoken.functions.approve(xbr.xbrchannel.address, amount).transact({'from': member_adr, 'gas': self._default_gas}) - allowance2 = xbr.xbrtoken.functions.allowance(member_adr, xbr.xbrchannel.address).call() - assert allowance2 - allowance1 == amount + from_adr = member_adr + to_adr = xbr.xbrchannel.address elif channel_type == ChannelType.PAYING: - allowance1 = xbr.xbrtoken.functions.allowance(marketmaker, xbr.xbrchannel.address).call() - xbr.xbrtoken.functions.approve(xbr.xbrchannel.address, amount).transact( - {'from': marketmaker, 'gas': self._default_gas}) - allowance2 = xbr.xbrtoken.functions.allowance(marketmaker, xbr.xbrchannel.address).call() - assert allowance2 - allowance1 == amount + from_adr = marketmaker + to_adr = xbr.xbrchannel.address else: assert False, 'should not arrive here' + # allowance1 = xbr.xbrtoken.functions.allowance(transact_from, xbr.xbrchannel.address).call() + # xbr.xbrtoken.functions.approve(to_adr, amount).transact( + # {'from': transact_from, 'gas': transact_gas}) + # allowance2 = xbr.xbrtoken.functions.allowance(transact_from, xbr.xbrchannel.address).call() + # assert allowance2 - allowance1 == amount + + try: + txn_hash = await deferToThread(self._send_Allowance, from_adr, to_adr, amount) + self.log.info('transaction submitted, txn_hash={txn_hash}', txn_hash=txn_hash) + except Exception as e: + self.log.failure() + raise e + # compute EIP712 signature, and sign using member private key signature = sign_eip712_channel_open(member_key, verifying_chain_id, verifying_contract_adr, channel_type, current_block_number, market_oid.bytes, channel_oid.bytes, @@ -637,11 +722,60 @@ async def _do_open_channel(self, market_oid, channel_oid, channel_type, delegate self.log.info('Channel open request submitted:\n\n{channel_request}\n', channel_request=pformat(channel_request)) + def _send_Allowance(self, from_adr, to_adr, amount): + # FIXME: estimate gas required for call + gas = self._default_gas + gasPrice = self._w3.toWei('10', 'gwei') + + from_adr = self._ethadr + + # each submitted transaction must contain a nonce, which is obtained by the on-chain transaction number + # for this account, including pending transactions (I think ..;) .. + nonce = self._w3.eth.getTransactionCount(from_adr, block_identifier='pending') + self.log.info('{func}::[1/4] - Ethereum transaction nonce: nonce={nonce}', + func=hltype(self._send_Allowance), + nonce=nonce) + + # serialize transaction raw data from contract call and transaction settings + raw_transaction = xbr.xbrtoken.functions.approve(to_adr, amount).buildTransaction({ + 'from': from_adr, + 'gas': gas, + 'gasPrice': gasPrice, + 'chainId': self._chain_id, # https://stackoverflow.com/a/57901206/884770 + 'nonce': nonce, + }) + self.log.info( + '{func}::[2/4] - Ethereum transaction created: raw_transaction=\n{raw_transaction}\n', + func=hltype(self._send_Allowance), + raw_transaction=raw_transaction) + + # compute signed transaction from above serialized raw transaction + signed_txn = self._w3.eth.account.sign_transaction(raw_transaction, private_key=self._ethkey_raw) + self.log.info( + '{func}::[3/4] - Ethereum transaction signed: signed_txn=\n{signed_txn}\n', + func=hltype(self._send_Allowance), + signed_txn=hlval(binascii.b2a_hex(signed_txn.rawTransaction).decode())) + + # now send the pre-signed transaction to the blockchain via the gateway .. + # https://web3py.readthedocs.io/en/stable/web3.eth.html # web3.eth.Eth.sendRawTransaction + txn_hash = self._w3.eth.sendRawTransaction(signed_txn.rawTransaction) + txn_hash = bytes(txn_hash) + self.log.info( + '{func}::[4/4] - Ethereum transaction submitted: txn_hash=0x{txn_hash}', + func=hltype(self._send_Allowance), + txn_hash=hlval(binascii.b2a_hex(txn_hash).decode())) + + return txn_hash + def _main(): parser = argparse.ArgumentParser() - parser.add_argument('command', type=str, choices=_COMMANDS, + parser.add_argument('command', + type=str, + choices=_COMMANDS, + const='noop', + nargs='?', help='Command to run') parser.add_argument('-d', @@ -777,42 +911,71 @@ def _main(): args = parser.parse_args() - if args.debug: - txaio.start_logging(level='debug') - else: - txaio.start_logging(level='info') - - extra = { - 'command': args.command, - 'ethkey': binascii.a2b_hex(args.ethkey[2:]), - 'cskey': binascii.a2b_hex(args.cskey[2:]), - 'username': args.username, - 'email': args.email, - 'market': uuid.UUID(args.market) if args.market else None, - 'market_title': args.market_title, - 'market_label': args.market_label, - 'market_homepage': args.market_homepage, - 'market_provider_security': args.provider_security or 0, - 'market_consumer_security': args.consumer_security or 0, - 'market_fee': args.market_fee or 0, - 'marketmaker': binascii.a2b_hex(args.marketmaker[2:]) if args.marketmaker else None, - 'actor_type': args.actor_type, - 'vcode': args.vcode, - 'vaction': uuid.UUID(args.vaction) if args.vaction else None, - 'channel': uuid.UUID(args.channel) if args.channel else None, - 'channel_type': args.channel_type, - 'delegate': binascii.a2b_hex(args.delegate[2:]) if args.delegate else None, - 'amount': args.amount or 0, - } - runner = ApplicationRunner(url=args.url, realm=args.realm, extra=extra, serializers=[CBORSerializer()]) - - try: - runner.run(Client, auto_reconnect=False) - except Exception as e: - print(e) - sys.exit(1) + if args.command == 'version': + print('') + print(' XBR CLI v{}\n'.format(__version__)) + print(' XBRToken contract address: {} [source: {}]'.format(hlid(XBR_DEBUG_TOKEN_ADDR), XBR_DEBUG_TOKEN_ADDR_SRC)) + print(' XBRNetwork contract address: {} [source: {}]'.format(hlid(XBR_DEBUG_NETWORK_ADDR), XBR_DEBUG_NETWORK_ADDR_SRC)) + print(' XBRMarket contract address: {} [source: {}]'.format(hlid(XBR_DEBUG_MARKET_ADDR), XBR_DEBUG_MARKET_ADDR_SRC)) + print(' XBRCatalog contract address: {} [source: {}]'.format(hlid(XBR_DEBUG_CATALOG_ADDR), XBR_DEBUG_CATALOG_ADDR_SRC)) + print(' XBRChannel contract address: {} [source: {}]'.format(hlid(XBR_DEBUG_CHANNEL_ADDR), XBR_DEBUG_CHANNEL_ADDR_SRC)) + print('') else: - sys.exit(0) + if args.command is None or args.command == 'noop': + print('no command given. select from: {}'.format(', '.join(_COMMANDS))) + sys.exit(0) + + # read or create a user profile + profile = load_or_create_profile() + + # only start txaio logging after above, which runs click (interactively) + if args.debug: + txaio.start_logging(level='debug') + else: + txaio.start_logging(level='info') + + log = txaio.make_logger() + + log.info('XBR CLI {version}', version=hlid('v' + __version__)) + log.info('Profile {profile} loaded from {path}', profile=hlval(profile.name), path=hlval(profile.path)) + + extra = { + # user profile and defaults + 'profile': profile, + + # allow to override, and add more arguments from the command line + 'command': args.command, + 'ethkey': binascii.a2b_hex(args.ethkey[2:]) if args.ethkey else None, + 'cskey': binascii.a2b_hex(args.cskey[2:]) if args.cskey else None, + 'username': args.username, + 'email': args.email, + 'market': uuid.UUID(args.market) if args.market else None, + 'market_title': args.market_title, + 'market_label': args.market_label, + 'market_homepage': args.market_homepage, + 'market_provider_security': args.provider_security or 0, + 'market_consumer_security': args.consumer_security or 0, + 'market_fee': args.market_fee or 0, + 'marketmaker': binascii.a2b_hex(args.marketmaker[2:]) if args.marketmaker else None, + 'actor_type': args.actor_type, + 'vcode': args.vcode, + 'vaction': uuid.UUID(args.vaction) if args.vaction else None, + 'channel': uuid.UUID(args.channel) if args.channel else None, + 'channel_type': args.channel_type, + 'delegate': binascii.a2b_hex(args.delegate[2:]) if args.delegate else None, + 'amount': args.amount or 0, + } + runner = ApplicationRunner(url=args.url, realm=args.realm, extra=extra, serializers=[CBORSerializer()]) + + try: + log.info('Connecting to "{url}" {realm} ..', + url=hlval(args.url), realm=('at realm "' + hlval(args.realm) + '"' if args.realm else '')) + runner.run(Client, auto_reconnect=False) + except Exception as e: + print(e) + sys.exit(1) + else: + sys.exit(0) if __name__ == '__main__': diff --git a/autobahn/xbr/_config.py b/autobahn/xbr/_config.py new file mode 100644 index 000000000..7644001ae --- /dev/null +++ b/autobahn/xbr/_config.py @@ -0,0 +1,304 @@ +############################################################################### +# +# The MIT License (MIT) +# +# Copyright (c) Crossbar.io Technologies 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 +import sys +import binascii +import configparser + +import click +import web3 +from eth_utils.conversions import hexstr_if_str, to_hex + +from txaio import make_logger +from autobahn.websocket.util import parse_url +from autobahn.xbr._util import hlval, hltype + + +_HAS_COLOR_TERM = False +try: + import colorama + + # https://github.com/tartley/colorama/issues/48 + term = None + if sys.platform == 'win32' and 'TERM' in os.environ: + term = os.environ.pop('TERM') + + colorama.init() + _HAS_COLOR_TERM = True + + if term: + os.environ['TERM'] = term + +except ImportError: + pass + + +class Profile(object): + + log = make_logger() + + def __init__(self, + path=None, + name=None, + ethkey=None, + cskey=None, + market_url=None, + market_realm=None, + infura_url=None, + infura_network=None, + infura_key=None, + infura_secret=None): + self.path = path + self.name = name + self.ethkey = ethkey + self.cskey = cskey + self.market_url = market_url + self.market_realm = market_realm + self.infura_url = infura_url + self.infura_network = infura_network + self.infura_key = infura_key + self.infura_secret = infura_secret + + @staticmethod + def parse(path, name, items): + ethkey = None + cskey = None + market_url = None + market_realm = None + infura_network = None + infura_key = None + infura_secret = None + infura_url = None + for k, v in items: + if k == 'market_url': + market_url = str(v) + elif k == 'market_realm': + market_realm = str(v) + elif k == 'ethkey': + ethkey = binascii.a2b_hex(v[2:]) + elif k == 'cskey': + cskey = binascii.a2b_hex(v[2:]) + elif k == 'infura_network': + infura_network = str(v) + elif k == 'infura_key': + infura_key = str(v) + elif k == 'infura_secret': + infura_secret = str(v) + elif k == 'infura_url': + infura_url = str(v) + else: + # skip unknown attribute + Profile.log.warn('unprocessed config attribute "{}"'.format(k)) + + return Profile(path, name, ethkey, cskey, market_url, market_realm, infura_url, infura_network, infura_key, infura_secret) + + +class UserConfig(object): + + log = make_logger() + + def __init__(self, config_path): + self._config_path = os.path.abspath(config_path) + + config = configparser.ConfigParser() + config.read(config_path) + + self.config = config + + profiles = {} + for profile_name in config.sections(): + profile = Profile.parse(config_path, profile_name, config.items(profile_name)) + profiles[profile_name] = profile + + self.profiles = profiles + + self.log.debug('profiles loaded: {profiles}', + func=hltype(self.__init__), + profiles=', '.join(hlval(x) for x in sorted(self.profiles.keys()))) + + +if 'CROSSBAR_FABRIC_URL' in os.environ: + _DEFAULT_CFC_URL = os.environ['CROSSBAR_FABRIC_URL'] +else: + _DEFAULT_CFC_URL = u'wss://master.xbr.network/ws' + + +def style_error(text): + if _HAS_COLOR_TERM: + return click.style(text, fg='red', bold=True) + else: + return text + + +def style_ok(text): + if _HAS_COLOR_TERM: + return click.style(text, fg='green', bold=True) + else: + return text + + +class WampUrl(click.ParamType): + """ + WAMP transport URL validator. + """ + + name = 'WAMP transport URL' + + def __init__(self): + click.ParamType.__init__(self) + + def convert(self, value, param, ctx): + try: + parse_url(value) + except Exception as e: + self.fail(style_error(str(e))) + else: + return value + + +def prompt_for_wamp_url(msg): + """ + Prompt user for WAMP transport URL (eg "wss://planet.xbr.network/ws"). + """ + value = click.prompt(msg, type=WampUrl()) + return value + + +class EthereumAddress(click.ParamType): + """ + Ethereum address validator. + """ + + name = 'Ethereum address' + + def __init__(self): + click.ParamType.__init__(self) + + def convert(self, value, param, ctx): + try: + value = web3.Web3.toChecksumAddress(value) + adr = binascii.a2b_hex(value[2:]) + if len(value) != 20: + raise ValueError('Ethereum addres must be 20 bytes (160 bit), but was {} bytes'.format(len(adr))) + except Exception as e: + self.fail(style_error(str(e))) + else: + return value + + +def prompt_for_ethereum_address(msg): + """ + Prompt user for an Ethereum (public) address. + """ + value = click.prompt(msg, type=EthereumAddress()) + return value + + +class PrivateKey(click.ParamType): + """ + Private key (32 bytes in HEX) validator. + """ + + name = 'Private key' + + def __init__(self, key_len): + click.ParamType.__init__(self) + self._key_len = key_len + + def convert(self, value, param, ctx): + try: + value = hexstr_if_str(to_hex, value) + key = binascii.a2b_hex(value[2:]) + if len(key) != self._key_len: + raise ValueError('key length must be {} bytes, but was {} bytes'.format(self._key_len, len(key))) + except Exception as e: + self.fail(style_error(str(e))) + else: + return value + + +def prompt_for_key(msg, key_len): + """ + Prompt user for a binary key of given length (in HEX). + """ + value = click.prompt(msg, type=PrivateKey(key_len)) + return value + + +# default configuration stored in $HOME/.xbrnetwork/config.ini +_DEFAULT_CONFIG = """[default] + +# user private Ethereum key +ethkey={ethkey} + +# user private WAMP client key +cskey={cskey} + +# default XBR market URL to connect to +market_url={market_url} + +# Infura blockchain gateway configuration +infura_url={infura_url} +infura_network={infura_network} +infura_key={infura_key} +infura_secret={infura_secret} +""" + + +def load_or_create_profile(dotdir=None, profile=None): + dotdir = dotdir or '~/.xbrnetwork' + profile = profile or 'default' + + config_dir = os.path.expanduser(dotdir) + if not os.path.isdir(config_dir): + os.mkdir(config_dir) + click.echo('created new local user directory {}'.format(style_ok(config_dir))) + + config_path = os.path.join(config_dir, 'config.ini') + if not os.path.isfile(config_path): + click.echo('creating new user profile "{}"'.format(style_ok(profile))) + with open(config_path, 'w') as f: + market_url = prompt_for_wamp_url('enter a XBR data market URL') + market_realm = click.prompt('enter the WAMP realm of the XBR data market', type=str) + ethkey = prompt_for_key('your private Etherum key', 32) + cskey = prompt_for_key('your private WAMP client key', 32) + infura_network = click.prompt('enter your Infura gateway URL', type=str, default='rinkeby') + infura_url = click.prompt('enter your Infura gateway URL', type=str) + infura_key = click.prompt('your Infura gateway key', type=str) + infura_secret = click.prompt('your Infura gateway secret', type=str) + f.write(_DEFAULT_CONFIG.format(market_url=market_url, market_realm=market_realm, ethkey=ethkey, + cskey=cskey, infura_url=infura_url, infura_network=infura_network, + infura_key=infura_key, infura_secret=infura_secret)) + click.echo('created new local user configuration {}'.format(style_ok(config_path))) + + config_obj = UserConfig(config_path) + profile_obj = config_obj.profiles.get(profile, None) + + if not profile_obj: + raise click.ClickException('no such profile: "{}"'.format(profile)) + + return profile_obj diff --git a/autobahn/xbr/_seller.py b/autobahn/xbr/_seller.py index 7d5b3a4c2..0d156ab80 100644 --- a/autobahn/xbr/_seller.py +++ b/autobahn/xbr/_seller.py @@ -52,7 +52,7 @@ class KeySeries(object): and key offering (to the XBR market maker). """ - def __init__(self, api_id, price, interval, on_rotate=None): + def __init__(self, api_id, price, interval=None, count=None, on_rotate=None): """ :param api_id: ID of the API for which to generate keys. @@ -61,20 +61,27 @@ def __init__(self, api_id, price, interval, on_rotate=None): :param price: Price per key in key series. :type price: int - :param interval: Key rotation interval in seconds. + :param interval: Interval in seconds after which to auto-rotate key. :type interval: int + :param count: Number of encryption operations after which to auto-rotate key. + :type count: int + :param on_rotate: Optional user callback fired after key was rotated. :type on_rotate: callable """ assert type(api_id) == bytes and len(api_id) == 16 assert type(price) == int and price > 0 - assert type(interval) == int and interval > 0 + assert interval is None or (type(interval) == int and interval > 0) + assert count is None or (type(count) == int and count > 0) + assert (interval is None and count is not None) or (interval is not None and count is None) assert on_rotate is None or callable(on_rotate) self._api_id = api_id self._price = price self._interval = interval + self._count = count + self._count_current = 0 self._on_rotate = on_rotate self._id = None @@ -93,7 +100,7 @@ def key_id(self): """ return self._id - def encrypt(self, payload): + async def encrypt(self, payload): """ Encrypt data with the current XBR data encryption key. @@ -104,6 +111,13 @@ def encrypt(self, payload): :rtype: bytes """ data = cbor2.dumps(payload) + + if self._count is not None: + self._count_current += 1 + if self._count_current >= self._count: + await self._rotate() + self._count_current = 0 + ciphertext = self._box.encrypt(data) return self._id, 'cbor', ciphertext @@ -261,7 +275,7 @@ def public_key(self): """ return self._pkey.public_key - def add(self, api_id, prefix, price, interval, categories=None): + def add(self, api_id, prefix, price, interval=None, count=None, categories=None): """ Add a new (rotating) private encryption key for encrypting data on the given API. @@ -271,12 +285,17 @@ def add(self, api_id, prefix, price, interval, categories=None): :param price: Price in XBR token per key. :type price: int - :param interval: Interval (in seconds) in which to auto-rotate the encryption key. + :param interval: Interval (in seconds) after which to auto-rotate the encryption key. :type interval: int + + :param count: Number of encryption operations after which to auto-rotate the encryption key. + :type count: int """ assert type(api_id) == bytes and len(api_id) == 16 and api_id not in self._keys assert type(price) == int and price > 0 - assert type(interval) == int and interval > 0 + assert interval is None or (type(interval) == int and interval > 0) + assert count is None or (type(count) == int and count > 0) + assert (interval is None and count is not None) or (interval is not None and count is None) assert categories is None or (type(categories) == dict and (type(k) == str for k in categories.keys()) and (type(v) == str for v in categories.values())), 'invalid categories type (must be dict) or category key or value type (must both be string)' async def on_rotate(key_series): @@ -340,7 +359,7 @@ async def on_rotate(key_series): self.log.warn('Failed to place offer for key! Retrying {retries}/5 ..', retries=retries) await asyncio.sleep(1) - key_series = self.KeySeries(api_id, price, interval, on_rotate) + key_series = self.KeySeries(api_id, price, interval=interval, count=count, on_rotate=on_rotate) self._keys[api_id] = key_series self.log.debug('Created new key series {key_series}', key_series=key_series) @@ -486,7 +505,7 @@ async def wrap(self, api_id, uri, payload): keyseries = self._keys[api_id] - key_id, serializer, ciphertext = keyseries.encrypt(payload) + key_id, serializer, ciphertext = await keyseries.encrypt(payload) return key_id, serializer, ciphertext diff --git a/docs/_static/screenshots/rinkeby-faucet.png b/docs/_static/screenshots/rinkeby-faucet.png new file mode 100644 index 000000000..e9443eaf4 Binary files /dev/null and b/docs/_static/screenshots/rinkeby-faucet.png differ diff --git a/docs/_static/screenshots/xbr-metamask-1.png b/docs/_static/screenshots/xbr-metamask-1.png new file mode 100644 index 000000000..61bcfee75 Binary files /dev/null and b/docs/_static/screenshots/xbr-metamask-1.png differ diff --git a/docs/_static/screenshots/xbr-metamask-2.png b/docs/_static/screenshots/xbr-metamask-2.png new file mode 100644 index 000000000..5f7573129 Binary files /dev/null and b/docs/_static/screenshots/xbr-metamask-2.png differ diff --git a/docs/_static/screenshots/xbr-metamask-3.png b/docs/_static/screenshots/xbr-metamask-3.png new file mode 100644 index 000000000..a403ddef6 Binary files /dev/null and b/docs/_static/screenshots/xbr-metamask-3.png differ diff --git a/docs/xbr-cli.rst b/docs/xbr-cli.rst index 89fc02f36..55bc07d4e 100644 --- a/docs/xbr-cli.rst +++ b/docs/xbr-cli.rst @@ -1,7 +1,52 @@ XBR Command line interface ========================== -Autobahn includes a command-line interface for the `XBR network `__: +Autobahn includes a command-line interface for the `XBR network `__. + +Status +------ + +* ``register-member`` +* ``register-member-verify`` +* ``get-member`` +* ``join-market`` +* ``join-market-verify`` +* ``get-actor`` +* ``create-market`` +* ``create-market-verify`` +* ``get-market`` +* ``open-channel`` +* ``get-channel`` + + +Prerequisites +------------- + +Installation +............ + +The XBR CLI (included in the `xbr` install flavor of Autobahn) can be installed using pip: + +.. code-block:: console + + $ pip install autobahn[all] + +To run the CLI and check for the installed version: + + +.. code-block:: console + + $ xbrnetwork version + + XBR CLI v20.5.1.dev1: + + XBRToken contract address: 0xCfEB869F69431e42cdB54A4F4f105C19C080A601 + XBRNetwork contract address: 0xC89Ce4735882C9F0f0FE26686c53074E09B0D550 + XBRMarket contract address: 0x9561C133DD8580860B6b7E504bC5Aa500f0f06a7 + XBRCatalog contract address: 0xD833215cBcc3f914bD1C9ece3EE7BF8B14f841bb + XBRChannel contract address: 0xe982E462b094850F12AF94d21D470e21bE9D0E9C + +To get help on the available commands: .. code-block:: console @@ -48,497 +93,182 @@ Autobahn includes a command-line interface for the `XBR network `__, and all XBR data markets, market operators +and actors (buyers & sellers) in markets are registered on the Ethereum blockchain. + +.. note:: + + Currently, XBR is still in alpha, and the latest version is XBR v20.4.2 deployed on Rinkeby testnet. + XBR will be deployed on mainnet with the official stable release. + +Market operators and market actors (buyers & sellers) maintain their (potentially anonymous) identity +via crypto wallets where the private wallet key is under exclusive access to the operator or actor. + +Running your own crypto wallet is easy using `MetaMask `__, a browser plugin that runs +in Chrome and Firefox. + +First step is to install MetaMask, creating a new wallet: + +.. image:: _static/screenshots/xbr-metamask-1.png + +and connect to `Rinkeby testnet `__: + +.. image:: _static/screenshots/xbr-metamask-2.png + +Then, to use your Ethereum private key with the XBR CLI, export the private key: + +.. image:: _static/screenshots/xbr-metamask-3.png .. code-block:: console - $ xbrnetwork --cskey=0x6cba0... --ethkey=0x7584... --username=oberstet1 --email=tobias.oberstein@gmail.com onboard - - 2020-04-21T12:00:33+0200 Client.__init__(config=ComponentConfig(realm=, extra={'command': 'onboard', 'ethkey': b'u\x84\x8d\xdb\x11U\xcd\x1c\xdfmt\xa6\xe7\xfb\xed\x06\xae\xaa!\xef-\x8a\x05\xdfz\xf2\xd9\\\xdc\x12vr', 'cskey': b'l\xba\x0f\x9c\xec\x8b)) - 2020-04-21T12:00:33+0200 Client (delegate) Ethereum key loaded (adr=0x0xecdb40C2B34f3bA162C413CC53BA3ca99ff8A047) - 2020-04-21T12:00:33+0200 Client (delegate) WAMP-cryptosign authentication key loaded (pubkey=0xe545a23b971a624d735f75ecf88676aa5170c14c4bc03bf31e88faaa7b28187f) - 2020-04-21T12:00:33+0200 Client.onConnect() - 2020-04-21T12:00:33+0200 Client.onChallenge(challenge=Challenge(method=cryptosign, extra={'challenge': '19c446edc6c87924814790fea75a0487ced6b7a6736d763e3b9f5d5ff4fdd078', 'channel_binding': 'tls-unique'})) - 2020-04-21T12:00:33+0200 Client.onJoin(details= - SessionDetails(realm="xbrnetwork", - session=7150418774024691, - authid="anonymous-QMM6-N4QH-4NSM-Y3NL-FKHH-HK6N", - authrole="anonymous", - authmethod="cryptosign", - authprovider="dynamic", - authextra={'x_cb_node': '5f1fcfbd-64d6-4929-949d-ad6cada0ea0b', 'x_cb_worker': 'rtr1', 'x_cb_peer': 'tcp4:213.170.219.39:8848', 'x_cb_pid': 2027}, - serializer="cbor", - transport="websocket", - resumed=None, - resumable=None, - resume_token=None)) - 2020-04-21T12:00:33+0200 not yet a member in the XBR network - 2020-04-21T12:00:39+0200 On-boarding member - verification "276450ce-cf17-4053-a83e-1a9ec053b4f8" created - 2020-04-21T12:00:39+0200 Client.onLeave(details=CloseDetails(reason=, message='None')) - 2020-04-21T12:00:39+0200 Shutting down .. - 2020-04-21T12:00:39+0200 Client.onDisconnect() - 2020-04-21T12:00:39+0200 Main loop terminated. - -Verify the on-boarding request: + --ethkey=0x4C1F... + +.. note:: + + Obviously, you must protect your *private key*! The *public address* of your wallet is not security + sensitive. Even the public address however should always be treated carefully regarding privacy. + +Finally, for testing on Rinkeby, get yourself some Ether from the `Rinkeby faucet `__: + +.. image:: _static/screenshots/rinkeby-faucet.png + +If you want to use the accounts from your MetaMask wallet derived from your wallet's seedphrase, you can +use a helper included with Autobahn to derive private keys for all accounts, eg account `0`: .. code-block:: console - $ xbrnetwork --cskey=0x6cba0... --ethkey=0x7584... --vaction=276450ce-cf17-4053-a83e-1a9ec053b4f8 --vcode=TFMC-KPRR-NNVE onboard-verify - - 2020-04-21T12:02:24+0200 Client.__init__(config=ComponentConfig(realm=, extra={'command': 'onboard-verify', 'ethkey': b'u\x84\x8d\xdb\x11U\xcd\x1c\xdfmt\xa6\xe7\xfb\xed\x06\xae\xaa!\xef-\x8a\x05\xdfz\xf2\xd9\\\xdc\x12vr', 'cskey': b'l\xba\x0f\x9c\xec\x8b)) - 2020-04-21T12:02:24+0200 Client (delegate) Ethereum key loaded (adr=0x0xecdb40C2B34f3bA162C413CC53BA3ca99ff8A047) - 2020-04-21T12:02:24+0200 Client (delegate) WAMP-cryptosign authentication key loaded (pubkey=0xe545a23b971a624d735f75ecf88676aa5170c14c4bc03bf31e88faaa7b28187f) - 2020-04-21T12:02:24+0200 Client.onConnect() - 2020-04-21T12:02:25+0200 Client.onChallenge(challenge=Challenge(method=cryptosign, extra={'challenge': 'ee7b0c616532c0000748cf699d63ec8579bdb20a793f3d8a08dc3711deaff563', 'channel_binding': 'tls-unique'})) - 2020-04-21T12:02:25+0200 Client.onJoin(details= - SessionDetails(realm="xbrnetwork", - session=1192999217896284, - authid="anonymous-6HTR-KUTW-VKAL-AWVU-H6S6-HWH3", - authrole="anonymous", - authmethod="cryptosign", - authprovider="dynamic", - authextra={'x_cb_node': '5f1fcfbd-64d6-4929-949d-ad6cada0ea0b', 'x_cb_worker': 'rtr1', 'x_cb_peer': 'tcp4:213.170.219.39:8858', 'x_cb_pid': 2027}, - serializer="cbor", - transport="websocket", - resumed=None, - resumable=None, - resume_token=None)) - 2020-04-21T12:02:25+0200 not yet a member in the XBR network - 2020-04-21T12:02:25+0200 Verifying member using vaction_oid=276450ce-cf17-4053-a83e-1a9ec053b4f8, vaction_code=TFMC-KPRR-NNVE .. - 2020-04-21T12:02:25+0200 SUCCESS! New XBR Member onboarded: member_oid=d08e6a3a-4748-4228-8737-d1e38d2dbfd8, result= - {'created': 1587463345067963095, - 'member_oid': b'\xd0\x8ej:GHB(\x877\xd1\xe3\x8d-\xbf\xd8', - 'transaction': b'\xfc#\xf6\x98\x9f}V!\x93\xf9\xdcq\x10\x9e\x91\x00' - b'\x8a\xd2\xf4\xe6+K\x7f\xed\x81.M\x1e\x1cb&9'} - 2020-04-21T12:02:25+0200 Client.onLeave(details=CloseDetails(reason=, message='None')) - 2020-04-21T12:02:25+0200 Shutting down .. - 2020-04-21T12:02:25+0200 Client.onDisconnect() - 2020-04-21T12:02:25+0200 Main loop terminated. - - -Get member ----------- - -To get member information (about oneself): + >>> from autobahn.xbr import account_from_seedphrase + >>> acct = account_from_seedphrase('myth like bonus scare over problem client lizard pioneer submit female collect', 0) + >>> acct.address + '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1' + >>> acct.privateKey.hex() + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d' + >>> + + +Client Key +.......... + +To connect to the XBR Network using the XBR CLI, the client (which connects via WAMP) needs a client private +key (used for WAMP-cryptosign authentication). + +A new key can be created by generating 32 random bytes: .. code-block:: console - $ xbrnetwork --cskey=0xfbb... --ethkey=0x5be59... get-member - - 2020-04-21T14:51:26+0200 Client.__init__(config=ComponentConfig(realm=, extra={'command': 'get-member', 'ethkey': b'[\xe5\x99\xa3I\'\xa1\x11\t"\xd7pK\xa3\x16\x14K1i\x9d\x8e\x7f"\x9e&\x84\xd5WZ\x84!N', 'cskey': b"\xfb\xb1\xd2\x08\x0c.\x1d\xaa\x8e)'+~\xc7\xe7K.#=\x1b\xda\xa4\xa3h>\xa7\x9d#<\xd6u\x89", 'username': None, 'email': None, 'market': None, 'marketmaker': None, 'actor_type': None, 'vcode': None, 'vaction': None}, keyring=None, controller=None, shared=None, runner=)) - 2020-04-21T14:51:26+0200 Client (delegate) Ethereum key loaded (adr=0x0x2F070c2f49a59159A0346396f1139203355ACA43) - 2020-04-21T14:51:26+0200 Client (delegate) WAMP-cryptosign authentication key loaded (pubkey=0x7e8956c3242a687470992175f950857679956e2ff49bf994bfeece491fd8a21d) - 2020-04-21T14:51:26+0200 Client.onConnect() - 2020-04-21T14:51:27+0200 Client.onChallenge(challenge=Challenge(method=cryptosign, extra={'challenge': '19fc396940262ec3bb12f5836bee0e71a0ba96e388ff107567b4c58ff87396b4', 'channel_binding': 'tls-unique'})) - 2020-04-21T14:51:27+0200 Client.onJoin(details= - SessionDetails(realm="xbrnetwork", - session=1273988983194228, - authid="member-eddcf37f-79cd-464f-b629-bf3c71f0ecce", - authrole="member", - authmethod="cryptosign", - authprovider="dynamic", - authextra={'x_cb_node': '5f1fcfbd-64d6-4929-949d-ad6cada0ea0b', 'x_cb_worker': 'rtr1', 'x_cb_peer': 'tcp4:213.170.219.39:10272', 'x_cb_pid': 2027}, - serializer="cbor", - transport="websocket", - resumed=None, - resumable=None, - resume_token=None)) - 2020-04-21T14:51:27+0200 already a member in the XBR network: - - {'address': b'/\x07\x0c/I\xa5\x91Y\xa04c\x96\xf1\x13\x92\x035Z\xcaC', - 'balance': {'eth': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x02\xc5K\xba\x10u\xa2\x00', - 'xbr': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00'}, - 'catalogs': 0, - 'created': 1587469642821232764, - 'domains': 0, - 'email': 'tobias.oberstein@gmail.com', - 'eula': 'QmRRvwEyT7oAM4rhGZFZXWQWNz1rEyiahgNuYy1Lxo4P6Z', - 'level': 1, - 'markets': 0, - 'oid': b'\xed\xdc\xf3\x7fy\xcdFO\xb6)\xbf, message='None')) - 2020-04-21T14:51:28+0200 Shutting down .. - 2020-04-21T14:51:28+0200 Client.onDisconnect() - 2020-04-21T14:51:28+0200 Main loop terminated. - - -Create market -------------- + $ openssl rand -hex 32 + ecdc5e97... -Submit request to create a new data market in the network: +When using the XBR CLI, provide your WAMP client key using the command line argument ``--cskey=0x`` appended +with your key: .. code-block:: console - $ xbrnetwork --cskey=0x6cba0... --ethkey=0x7584... --market=1388ddf6-fe36-4201-b1aa-cb7e36b4cfb3 --marketmaker=0x31C2891b219575F119ad4a9083C089153382F0A5 create-market - - 2020-04-21T12:54:38+0200 Client.__init__(config=ComponentConfig(realm=, extra={'command': 'create-market', 'ethkey': b'u\x84\x8d\xdb\x11U\xcd\x1c\xdfmt\xa6\xe7\xfb\xed\x06\xae\xaa!\xef-\x8a\x05\xdfz\xf2\xd9\\\xdc\x12vr', 'cskey': b'l\xba\x0f\x9c\xec\x8b)) - 2020-04-21T12:54:39+0200 Client (delegate) Ethereum key loaded (adr=0x0xecdb40C2B34f3bA162C413CC53BA3ca99ff8A047) - 2020-04-21T12:54:39+0200 Client (delegate) WAMP-cryptosign authentication key loaded (pubkey=0xe545a23b971a624d735f75ecf88676aa5170c14c4bc03bf31e88faaa7b28187f) - 2020-04-21T12:54:39+0200 Client.onConnect() - 2020-04-21T12:54:39+0200 Client.onChallenge(challenge=Challenge(method=cryptosign, extra={'challenge': '71d59158fd8720fd7da41c5587c7652838bb5e4a1f17220e476cc303ad13bbf4', 'channel_binding': 'tls-unique'})) - 2020-04-21T12:54:39+0200 Client.onJoin(details= - SessionDetails(realm="xbrnetwork", - session=783576629122096, - authid="member-d08e6a3a-4748-4228-8737-d1e38d2dbfd8", - authrole="member", - authmethod="cryptosign", - authprovider="dynamic", - authextra={'x_cb_node': '5f1fcfbd-64d6-4929-949d-ad6cada0ea0b', 'x_cb_worker': 'rtr1', 'x_cb_peer': 'tcp4:213.170.219.39:9160', 'x_cb_pid': 2027}, - serializer="cbor", - transport="websocket", - resumed=None, - resumable=None, - resume_token=None)) - 2020-04-21T12:54:39+0200 already a member in the XBR network: - - {'address': b'\xec\xdb@\xc2\xb3O;\xa1b\xc4\x13\xccS\xba<\xa9\x9f\xf8\xa0G', - 'balance': {'eth': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x02\xc50q%\x1d\xc2\x00', - 'xbr': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00'}, - 'catalogs': 0, - 'created': 1587463345067963095, - 'domains': 0, - 'email': 'tobias.oberstein@gmail.com', - 'eula': 'QmawsPbwU8aJPVrP4JSP5EooEhiaymxan6n6kYySWvv9wn', - 'level': 1, - 'markets': 0, - 'oid': b'\xd0\x8ej:GHB(\x877\xd1\xe3\x8d-\xbf\xd8', - 'profile': 'QmV1eeDextSdUrRUQp9tUXF8SdvVeykaiwYLgrXHHVyULY', - 'username': 'oberstet1'} - - 2020-04-21T12:54:41+0200 SUCCESS: Create market request submitted: - {'action': 'create_market', - 'timestamp': 1587466481552866698, - 'vaction_oid': b']mh\xac\xef\xa1L\xf7\x97\\y\x9a\xf5\xfdxN'} - - 2020-04-21T12:54:41+0200 SUCCESS: New Market verification "5d6d68ac-efa1-4cf7-975c-799af5fd784e" created - 2020-04-21T12:54:41+0200 Client.onLeave(details=CloseDetails(reason=, message='None')) - 2020-04-21T12:54:41+0200 Shutting down .. - 2020-04-21T12:54:41+0200 Client.onDisconnect() - 2020-04-21T12:54:41+0200 Main loop terminated. - -Verify the market creation request: + --cskey=0xecdc5e97... + + +Profile +------- + +To create a new user profile: .. code-block:: console - $ xbrnetwork --cskey=0x6cba0... --ethkey=0x7584... --vaction=5d6d68ac-efa1-4cf7-975c-799af5fd784e --vcode=VCKP-SJCP-MAJN create-market-verify - - 2020-04-21T12:55:56+0200 Client.__init__(config=ComponentConfig(realm=, extra={'command': 'create-market-verify', 'ethkey': b'u\x84\x8d\xdb\x11U\xcd\x1c\xdfmt\xa6\xe7\xfb\xed\x06\xae\xaa!\xef-\x8a\x05\xdfz\xf2\xd9\\\xdc\x12vr', 'cskey': b'l\xba\x0f\x9c\xec\x8b)) - 2020-04-21T12:55:56+0200 Client (delegate) Ethereum key loaded (adr=0x0xecdb40C2B34f3bA162C413CC53BA3ca99ff8A047) - 2020-04-21T12:55:56+0200 Client (delegate) WAMP-cryptosign authentication key loaded (pubkey=0xe545a23b971a624d735f75ecf88676aa5170c14c4bc03bf31e88faaa7b28187f) - 2020-04-21T12:55:56+0200 Client.onConnect() - 2020-04-21T12:55:56+0200 Client.onChallenge(challenge=Challenge(method=cryptosign, extra={'challenge': '6d3dc4ae0e506caac39c019972d2b6fa6359744159953bb0abff5bf066ee6492', 'channel_binding': 'tls-unique'})) - 2020-04-21T12:55:56+0200 Client.onJoin(details= - SessionDetails(realm="xbrnetwork", - session=7104052105792514, - authid="member-d08e6a3a-4748-4228-8737-d1e38d2dbfd8", - authrole="member", - authmethod="cryptosign", - authprovider="dynamic", - authextra={'x_cb_node': '5f1fcfbd-64d6-4929-949d-ad6cada0ea0b', 'x_cb_worker': 'rtr1', 'x_cb_peer': 'tcp4:213.170.219.39:9168', 'x_cb_pid': 2027}, - serializer="cbor", - transport="websocket", - resumed=None, - resumable=None, - resume_token=None)) - 2020-04-21T12:55:57+0200 already a member in the XBR network: - - {'address': b'\xec\xdb@\xc2\xb3O;\xa1b\xc4\x13\xccS\xba<\xa9\x9f\xf8\xa0G', - 'balance': {'eth': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x02\xc50q%\x1d\xc2\x00', - 'xbr': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00'}, - 'catalogs': 0, - 'created': 1587463345067963095, - 'domains': 0, - 'email': 'tobias.oberstein@gmail.com', - 'eula': 'QmawsPbwU8aJPVrP4JSP5EooEhiaymxan6n6kYySWvv9wn', - 'level': 1, - 'markets': 0, - 'oid': b'\xd0\x8ej:GHB(\x877\xd1\xe3\x8d-\xbf\xd8', - 'profile': 'QmV1eeDextSdUrRUQp9tUXF8SdvVeykaiwYLgrXHHVyULY', - 'username': 'oberstet1'} - - 2020-04-21T12:55:57+0200 Verifying create market using vaction_oid=5d6d68ac-efa1-4cf7-975c-799af5fd784e, vaction_code=VCKP-SJCP-MAJN .. - 2020-04-21T12:55:57+0200 Create market request verified: - {'created': 1587466557317337105, - 'market_oid': b'\x13\x88\xdd\xf6\xfe6B\x01\xb1\xaa\xcb~6\xb4\xcf\xb3', - 'transaction': b'\xb3z3\x0f\\\xc7\x11L\x9es\r\xc6\x85\xd2\x88,\x0f\x1b{\xed' - b'@\x89\xda\xb0\t\xdde\xdd\x8eh\xda\xaa'} - - 2020-04-21T12:55:57+0200 SUCCESS! New XBR market created: market_oid=1388ddf6-fe36-4201-b1aa-cb7e36b4cfb3, result= - {'created': 1587466557317337105, - 'market_oid': b'\x13\x88\xdd\xf6\xfe6B\x01\xb1\xaa\xcb~6\xb4\xcf\xb3', - 'transaction': b'\xb3z3\x0f\\\xc7\x11L\x9es\r\xc6\x85\xd2\x88,\x0f\x1b{\xed' - b'@\x89\xda\xb0\t\xdde\xdd\x8eh\xda\xaa'} - 2020-04-21T12:55:57+0200 SUCCESS - find_markets: found 2 markets - 2020-04-21T12:55:57+0200 SUCCESS - get_markets_by_owner: found 1 markets - 2020-04-21T12:55:57+0200 network.xbr.console.get_market(market_oid=b'\x13\x88\xdd\xf6\xfe6B\x01\xb1\xaa\xcb~6\xb4\xcf\xb3') .. - 2020-04-21T12:55:57+0200 SUCCESS: got market information - - {'attributes': {'homepage': 'https://markets.international-data-monetization-award.com/', - 'label': 'IDMA', - 'title': 'International Data Monetization Award'}, - 'coin': b'\x8dA\xefd\xd4\x9e\xa1U\x0bKA\xa8\x95\x9d\x85f\x01D\x15\x03', - 'consumer_security': None, - 'created': None, - 'maker': b'1\xc2\x89\x1b!\x95u\xf1\x19\xadJ\x90\x83\xc0\x89\x153\x82\xf0\xa5', - 'market': b'\x13\x88\xdd\xf6\xfe6B\x01\xb1\xaa\xcb~6\xb4\xcf\xb3', - 'market_fee': None, - 'meta': 'QmWPFjSR61eCHnJG5GEFJf8d4QW8LW3N3PFqo6RvC15QrA', - 'owner': b'\xec\xdb@\xc2\xb3O;\xa1b\xc4\x13\xccS\xba<\xa9\x9f\xf8\xa0G', - 'provider_security': None, - 'seq': 0, - 'signature': None, - 'terms': 'QmNXqk5yEbiUYHeDboeaJY6iCGVNm4MXr5uuYqpzSeVhVh', - 'tid': None, - 'timestamp': 1587466557317337105} - - 2020-04-21T12:55:57+0200 Client.onLeave(details=CloseDetails(reason=, message='None')) - 2020-04-21T12:55:57+0200 Shutting down .. - 2020-04-21T12:55:57+0200 Client.onDisconnect() - 2020-04-21T12:55:57+0200 Main loop terminated. - - -Join market + $ xbrnetwork + created new local user directory /home/oberstet/.xbrnetwork + creating new user profile "default" + enter a XBR data market URL: wss://markets.international-data-monetization-award.com/ws + enter the WAMP realm of the XBR data market: idma + your private Etherum key: 0x4C1F7... + your private WAMP client key: 0x7e8f... + your Infura gateway key: 40c69... + your Infura gateway secret: 55119... + created new local user configuration /home/oberstet/.xbrnetwork/config.ini + user profile "default" loaded + + +On-boarding ----------- -Submit new member on-boarding request: +To on-board and register in the XBR Network using the CLI, submit a request providing your Ethereum private key, your +client key, as well as your username and email: + +.. code-block:: console + + $ xbrnetwork register-member \ + --cskey=0x7e8f... \ + --ethkey=0x4C1F7... \ + --username=oberstet5 \ + --email=tobias.oberstein@gmail.com + +.. note:: + + Of course, neither your username nor your email is stored on-chain (on the blockchain). Your email is required so that + we can send a verification code to you (see next step). + +You should receive an email with a verification action ID such as ``072061e8-d1b4-4988-9524-6873b4d5784e`` and +a verification code such as ``5QRM-R5KR-7PGU``. + +Verify the on-boarding request using the verification action and code: .. code-block:: console - $ xbrnetwork --cskey=0xfbb1d... --ethkey=0x5be5... --username=oberstet2 --email=tobias.oberstein@gmail.com onboard - - 2020-04-21T13:46:13+0200 Client.__init__(config=ComponentConfig(realm=, extra={'command': 'onboard', 'ethkey': b'[\xe5\x99\xa3I\'\xa1\x11\t"\xd7pK\xa3\x16\x14K1i\x9d\x8e\x7f"\x9e&\x84\xd5WZ\x84!N', 'cskey': b"\xfb\xb1\xd2\x08\x0c.\x1d\xaa\x8e)'+~\xc7\xe7K.#=\x1b\xda\xa4\xa3h>\xa7\x9d#<\xd6u\x89", 'username': 'oberstet2', 'email': 'tobias.oberstein@gmail.com', 'market': None, 'marketmaker': None, 'actor_type': None, 'vcode': None, 'vaction': None}, keyring=None, controller=None, shared=None, runner=)) - 2020-04-21T13:46:13+0200 Client (delegate) Ethereum key loaded (adr=0x0x2F070c2f49a59159A0346396f1139203355ACA43) - 2020-04-21T13:46:13+0200 Client (delegate) WAMP-cryptosign authentication key loaded (pubkey=0x7e8956c3242a687470992175f950857679956e2ff49bf994bfeece491fd8a21d) - 2020-04-21T13:46:13+0200 Client.onConnect() - 2020-04-21T13:46:13+0200 Client.onChallenge(challenge=Challenge(method=cryptosign, extra={'challenge': '55523ac840f06ba9b7d6f51e1f479d4aacbd974e9f41badc4578777f6d7227f9', 'channel_binding': 'tls-unique'})) - 2020-04-21T13:46:13+0200 Client.onJoin(details= - SessionDetails(realm="xbrnetwork", - session=4495107774306724, - authid="anonymous-RY3A-4XYG-M767-U7SN-C3NM-USCF", - authrole="anonymous", - authmethod="cryptosign", - authprovider="dynamic", - authextra={'x_cb_node': '5f1fcfbd-64d6-4929-949d-ad6cada0ea0b', 'x_cb_worker': 'rtr1', 'x_cb_peer': 'tcp4:213.170.219.39:9616', 'x_cb_pid': 2027}, - serializer="cbor", - transport="websocket", - resumed=None, - resumable=None, - resume_token=None)) - 2020-04-21T13:46:13+0200 not yet a member in the XBR network - 2020-04-21T13:46:15+0200 On-boarding member - verification "8657b188-6936-4053-a970-42e4d9a866ee" created - 2020-04-21T13:46:15+0200 Client.onLeave(details=CloseDetails(reason=, message='None')) - 2020-04-21T13:46:15+0200 Shutting down .. - 2020-04-21T13:46:15+0200 Client.onDisconnect() - 2020-04-21T13:46:15+0200 Main loop terminated. - -Verify member on-boarding request: + $ xbrnetwork register-member-verify \ + --cskey=0x7e8f... \ + --ethkey=0x4C1F7... \ + --vaction=072061e8-d1b4-4988-9524-6873b4d5784e \ + --vcode=5QRM-R5KR-7PGU + +To access your member profile, run: .. code-block:: console - $ xbrnetwork --cskey=0xfbb1d... --ethkey=0x5be5... --vcode=5QJF-MK6F-QRVQ --vaction=8657b188-6936-4053-a970-42e4d9a866ee onboard-verify - - 2020-04-21T13:47:22+0200 Client.__init__(config=ComponentConfig(realm=, extra={'command': 'onboard-verify', 'ethkey': b'[\xe5\x99\xa3I\'\xa1\x11\t"\xd7pK\xa3\x16\x14K1i\x9d\x8e\x7f"\x9e&\x84\xd5WZ\x84!N', 'cskey': b"\xfb\xb1\xd2\x08\x0c.\x1d\xaa\x8e)'+~\xc7\xe7K.#=\x1b\xda\xa4\xa3h>\xa7\x9d#<\xd6u\x89", 'username': None, 'email': None, 'market': None, 'marketmaker': None, 'actor_type': None, 'vcode': '5QJF-MK6F-QRVQ', 'vaction': UUID('8657b188-6936-4053-a970-42e4d9a866ee')}, keyring=None, controller=None, shared=None, runner=)) - 2020-04-21T13:47:22+0200 Client (delegate) Ethereum key loaded (adr=0x0x2F070c2f49a59159A0346396f1139203355ACA43) - 2020-04-21T13:47:22+0200 Client (delegate) WAMP-cryptosign authentication key loaded (pubkey=0x7e8956c3242a687470992175f950857679956e2ff49bf994bfeece491fd8a21d) - 2020-04-21T13:47:22+0200 Client.onConnect() - 2020-04-21T13:47:22+0200 Client.onChallenge(challenge=Challenge(method=cryptosign, extra={'challenge': 'ef0f9b882ac8487b85d85aa4a4ac6e6bc2a50775bd59bc40caeda650c20d4ea4', 'channel_binding': 'tls-unique'})) - 2020-04-21T13:47:22+0200 Client.onJoin(details= - SessionDetails(realm="xbrnetwork", - session=1822866108991386, - authid="anonymous-Q4LE-5NHV-SQJP-LNMC-XKEY-FRKT", - authrole="anonymous", - authmethod="cryptosign", - authprovider="dynamic", - authextra={'x_cb_node': '5f1fcfbd-64d6-4929-949d-ad6cada0ea0b', 'x_cb_worker': 'rtr1', 'x_cb_peer': 'tcp4:213.170.219.39:9622', 'x_cb_pid': 2027}, - serializer="cbor", - transport="websocket", - resumed=None, - resumable=None, - resume_token=None)) - 2020-04-21T13:47:22+0200 not yet a member in the XBR network - 2020-04-21T13:47:22+0200 Verifying member using vaction_oid=8657b188-6936-4053-a970-42e4d9a866ee, vaction_code=5QJF-MK6F-QRVQ .. - 2020-04-21T13:47:23+0200 SUCCESS! New XBR Member onboarded: member_oid=eddcf37f-79cd-464f-b629-bf3c71f0ecce, result= - {'created': 1587469642821232764, - 'member_oid': b'\xed\xdc\xf3\x7fy\xcdFO\xb6)\xbf, message='None')) - 2020-04-21T13:47:23+0200 Shutting down .. - 2020-04-21T13:47:23+0200 Client.onDisconnect() - 2020-04-21T13:47:23+0200 Main loop terminated. - - -Submit market join request for new member: + xbrnetwork get-member \ + --cskey=0x7e8f... \ + --ethkey=0x4C1F7... + + +Joining a market +---------------- + +To join a XBR data market, you will need the XBR data market ID, such as ``1388ddf6-fe36-4201-b1aa-cb7e36b4cfb3`` +(which is the IDMA test market). + +Here is how to join as an actor in that market as both a buyer and seller: .. code-block:: console - $ xbrnetwork --cskey=0xfbb1d... --ethkey=0x5be5... --market=1388ddf6-fe36-4201-b1aa-cb7e36b4cfb3 --actor_type=3 join-market - - 2020-04-21T13:47:33+0200 Client.__init__(config=ComponentConfig(realm=, extra={'command': 'join-market', 'ethkey': b'[\xe5\x99\xa3I\'\xa1\x11\t"\xd7pK\xa3\x16\x14K1i\x9d\x8e\x7f"\x9e&\x84\xd5WZ\x84!N', 'cskey': b"\xfb\xb1\xd2\x08\x0c.\x1d\xaa\x8e)'+~\xc7\xe7K.#=\x1b\xda\xa4\xa3h>\xa7\x9d#<\xd6u\x89", 'username': None, 'email': None, 'market': UUID('1388ddf6-fe36-4201-b1aa-cb7e36b4cfb3'), 'marketmaker': None, 'actor_type': 3, 'vcode': None, 'vaction': None}, keyring=None, controller=None, shared=None, runner=)) - 2020-04-21T13:47:33+0200 Client (delegate) Ethereum key loaded (adr=0x0x2F070c2f49a59159A0346396f1139203355ACA43) - 2020-04-21T13:47:33+0200 Client (delegate) WAMP-cryptosign authentication key loaded (pubkey=0x7e8956c3242a687470992175f950857679956e2ff49bf994bfeece491fd8a21d) - 2020-04-21T13:47:33+0200 Client.onConnect() - 2020-04-21T13:47:33+0200 Client.onChallenge(challenge=Challenge(method=cryptosign, extra={'challenge': '8a7af41f88a793623f875b6111cc0001c4ef86d32f38885767dffab8d7fac698', 'channel_binding': 'tls-unique'})) - 2020-04-21T13:47:33+0200 Client.onJoin(details= - SessionDetails(realm="xbrnetwork", - session=2766315047838727, - authid="member-eddcf37f-79cd-464f-b629-bf3c71f0ecce", - authrole="member", - authmethod="cryptosign", - authprovider="dynamic", - authextra={'x_cb_node': '5f1fcfbd-64d6-4929-949d-ad6cada0ea0b', 'x_cb_worker': 'rtr1', 'x_cb_peer': 'tcp4:213.170.219.39:9626', 'x_cb_pid': 2027}, - serializer="cbor", - transport="websocket", - resumed=None, - resumable=None, - resume_token=None)) - 2020-04-21T13:47:33+0200 already a member in the XBR network: - - {'address': b'/\x07\x0c/I\xa5\x91Y\xa04c\x96\xf1\x13\x92\x035Z\xcaC', - 'balance': {'eth': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x02\xc5K\xba\x10u\xa2\x00', - 'xbr': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00'}, - 'catalogs': 0, - 'created': 1587469642821232764, - 'domains': 0, - 'email': 'tobias.oberstein@gmail.com', - 'eula': 'QmRRvwEyT7oAM4rhGZFZXWQWNz1rEyiahgNuYy1Lxo4P6Z', - 'level': 1, - 'markets': 0, - 'oid': b'\xed\xdc\xf3\x7fy\xcdFO\xb6)\xbf, message='None')) - 2020-04-21T13:47:35+0200 Shutting down .. - 2020-04-21T13:47:35+0200 Client.onDisconnect() - 2020-04-21T13:47:35+0200 Main loop terminated. - -Verify market join request for member: + $ xbrnetwork join-market \ + --cskey=0x7e8f... \ + --ethkey=0x4C1F7... \ + --market=1388ddf6-fe36-4201-b1aa-cb7e36b4cfb3 \ + --actor_type=3 + +You will receive an email with a verification action ID and a verification code. Submit these +to complete joining the market: .. code-block:: console - $ xbrnetwork --cskey=0xfbb1d... --ethkey=0x5be5... --vaction=44630f46-0ded-4eaf-90aa-9fbd2925788d --vcode=G3XA-PEX9-F4JV join-market-verify - - 2020-04-21T13:48:39+0200 Client.__init__(config=ComponentConfig(realm=, extra={'command': 'join-market-verify', 'ethkey': b'[\xe5\x99\xa3I\'\xa1\x11\t"\xd7pK\xa3\x16\x14K1i\x9d\x8e\x7f"\x9e&\x84\xd5WZ\x84!N', 'cskey': b"\xfb\xb1\xd2\x08\x0c.\x1d\xaa\x8e)'+~\xc7\xe7K.#=\x1b\xda\xa4\xa3h>\xa7\x9d#<\xd6u\x89", 'username': None, 'email': None, 'market': None, 'marketmaker': None, 'actor_type': None, 'vcode': 'G3XA-PEX9-F4JV', 'vaction': UUID('44630f46-0ded-4eaf-90aa-9fbd2925788d')}, keyring=None, controller=None, shared=None, runner=)) - 2020-04-21T13:48:39+0200 Client (delegate) Ethereum key loaded (adr=0x0x2F070c2f49a59159A0346396f1139203355ACA43) - 2020-04-21T13:48:39+0200 Client (delegate) WAMP-cryptosign authentication key loaded (pubkey=0x7e8956c3242a687470992175f950857679956e2ff49bf994bfeece491fd8a21d) - 2020-04-21T13:48:39+0200 Client.onConnect() - 2020-04-21T13:48:39+0200 Client.onChallenge(challenge=Challenge(method=cryptosign, extra={'challenge': '3170ea11ac8c490754efd3ecaabf6cfc49a34e0b987bccc9a1c4a29eb3fd659d', 'channel_binding': 'tls-unique'})) - 2020-04-21T13:48:39+0200 Client.onJoin(details= - SessionDetails(realm="xbrnetwork", - session=5153498254436248, - authid="member-eddcf37f-79cd-464f-b629-bf3c71f0ecce", - authrole="member", - authmethod="cryptosign", - authprovider="dynamic", - authextra={'x_cb_node': '5f1fcfbd-64d6-4929-949d-ad6cada0ea0b', 'x_cb_worker': 'rtr1', 'x_cb_peer': 'tcp4:213.170.219.39:9640', 'x_cb_pid': 2027}, - serializer="cbor", - transport="websocket", - resumed=None, - resumable=None, - resume_token=None)) - 2020-04-21T13:48:39+0200 already a member in the XBR network: - - {'address': b'/\x07\x0c/I\xa5\x91Y\xa04c\x96\xf1\x13\x92\x035Z\xcaC', - 'balance': {'eth': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x02\xc5K\xba\x10u\xa2\x00', - 'xbr': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00'}, - 'catalogs': 0, - 'created': 1587469642821232764, - 'domains': 0, - 'email': 'tobias.oberstein@gmail.com', - 'eula': 'QmRRvwEyT7oAM4rhGZFZXWQWNz1rEyiahgNuYy1Lxo4P6Z', - 'level': 1, - 'markets': 0, - 'oid': b'\xed\xdc\xf3\x7fy\xcdFO\xb6)\xbf, message='None')) - 2020-04-21T13:48:39+0200 Shutting down .. - 2020-04-21T13:48:39+0200 Client.onDisconnect() - 2020-04-21T13:48:39+0200 Main loop terminated. - - -Get actor ---------- - -To query for all markets a member as joined as an actor: + xbrnetwork join-market-verify \ + --cskey=0x7e8f... \ + --ethkey=0x4C1F7... \ + --vaction=ddcd5452-28cc-4ecb-a0f3-8fc8b596f9a5 \ + --vcode=AGGA-PK6G-57NY + +To access your actor status in a market, run: .. code-block:: console - $ xbrnetwork --ethkey=0xbd7f0... --cskey=0x9e1dadb... get-actor - - 2020-04-22T17:26:38+0200 Client.__init__(config=ComponentConfig(realm=, extra={'command': 'get-actor', 'ethkey': b'\xbd\x7f\x02\xa1\xca\x01I+\xfecG*\xdf\x18ZX"\xa6\xbc\xd9hh\x18\xb9\x8eM\xa9\xde\xc8rC\xcc', 'cskey': b'\x9e\x1d\xad\xb7\xd23\xb3QG \x06\xb4\x04\x9e\xc0\xd2T\x82m\x04X\x1b\xc8\xda)\xc4\xfc\xbc\xe4\x08\x97\x9a', 'username': None, 'email': None, 'market': None, 'market_title': None, 'market_label': None, 'market_homepage': None, 'market_provider_security': 0, 'market_consumer_security': 0, 'market_fee': 0, 'marketmaker': None, 'actor_type': None, 'vcode': None, 'vaction': None, 'channel': None, 'channel_type': None, 'delegate': None, 'amount': 0}, keyring=None, controller=None, shared=None, runner=)) - 2020-04-22T17:26:38+0200 Client (delegate) Ethereum key loaded (adr=0x0xAA8Cc377db31a354137d8Bb86D0E38495dbD5266) - 2020-04-22T17:26:38+0200 Client (delegate) WAMP-cryptosign authentication key loaded (pubkey=0xcffc2bfde59bd0441c166bacc3591c9e00ae88a8a6c828e6e698d7f58162c919) - 2020-04-22T17:26:38+0200 Client.onConnect() - 2020-04-22T17:26:38+0200 Client.onChallenge(challenge=Challenge(method=cryptosign, extra={'challenge': '57111fcf82404684888e60091ac2a74e459c17031d30c1e0d854741e2d70d251', 'channel_binding': 'tls-unique'})) - 2020-04-22T17:26:38+0200 Client.onJoin(details= - SessionDetails(realm="xbrnetwork", - session=3066427635559969, - authid="member-04d6ea0d-64fc-4e39-8555-46a8a57afa19", - authrole="member", - authmethod="cryptosign", - authprovider="dynamic", - authextra={'x_cb_node': '5f1fcfbd-64d6-4929-949d-ad6cada0ea0b', 'x_cb_worker': 'rtr1', 'x_cb_peer': 'tcp4:213.170.219.39:51114', 'x_cb_pid': 2027}, - serializer="cbor", - transport="websocket", - resumed=None, - resumable=None, - resume_token=None)) - 2020-04-22T17:26:38+0200 already a member in the XBR network: - - {'address': b'\xaa\x8c\xc3w\xdb1\xa3T\x13}\x8b\xb8m\x0e8I]\xbdRf', - 'balance': {'eth': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00', - 'xbr': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00'}, - 'catalogs': 0, - 'created': 1587566067769146003, - 'domains': 0, - 'email': 'tobias.oberstein@gmail.com', - 'eula': 'QmTMVPRGGTsJrsEkh6t4LDGYz5AZUv2dDMF9rrPXkKbAC5', - 'level': 1, - 'markets': 0, - 'oid': b'\x04\xd6\xea\rd\xfcN9\x85UF\xa8\xa5z\xfa\x19', - 'profile': 'QmV1eeDextSdUrRUQp9tUXF8SdvVeykaiwYLgrXHHVyULY', - 'username': 'oberstet3'} - - 2020-04-22T17:26:39+0200 Found member with address 0xAA8Cc377db31a354137d8Bb86D0E38495dbD5266, member level 1: 0 ETH, 0 XBR - 2020-04-22T17:26:39+0200 Member is actor in 1 markets! - 2020-04-22T17:26:39+0200 Actor is joined to market 6006f903-f993-4893-8b67-2e8534784ab7 (market owner 0x163d58ce482560b7826b4612f40aa2a7d53310c4) - 2020-04-22T17:26:39+0200 Client.onLeave(details=CloseDetails(reason=, message='None')) - 2020-04-22T17:26:39+0200 Shutting down .. - 2020-04-22T17:26:39+0200 Client.onDisconnect() - 2020-04-22T17:26:39+0200 Main loop terminated. + $ xbrnetwork get-actor \ + --cskey=0x7e8f... \ + --ethkey=0x4C1F7... \ + --market=1388ddf6-fe36-4201-b1aa-cb7e36b4cfb3 + + +Opening a channel +----------------- +