Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

_cli.py 51KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106
  1. ###############################################################################
  2. #
  3. # The MIT License (MIT)
  4. #
  5. # Copyright (c) typedef int GmbH
  6. #
  7. # Permission is hereby granted, free of charge, to any person obtaining a copy
  8. # of this software and associated documentation files (the "Software"), to deal
  9. # in the Software without restriction, including without limitation the rights
  10. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. # copies of the Software, and to permit persons to whom the Software is
  12. # furnished to do so, subject to the following conditions:
  13. #
  14. # The above copyright notice and this permission notice shall be included in
  15. # all copies or substantial portions of the Software.
  16. #
  17. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. # THE SOFTWARE.
  24. #
  25. ###############################################################################
  26. import os
  27. import sys
  28. import pkg_resources
  29. from jinja2 import Environment, FileSystemLoader
  30. from autobahn import xbr
  31. from autobahn import __version__
  32. if not xbr.HAS_XBR:
  33. print("\nYou must install the [xbr] extra to use xbrnetwork")
  34. print("For example, \"pip install autobahn[xbr]\".")
  35. sys.exit(1)
  36. from autobahn.xbr._abi import XBR_DEBUG_TOKEN_ADDR, XBR_DEBUG_NETWORK_ADDR, XBR_DEBUG_DOMAIN_ADDR, \
  37. XBR_DEBUG_CATALOG_ADDR, XBR_DEBUG_MARKET_ADDR, XBR_DEBUG_CHANNEL_ADDR
  38. from autobahn.xbr._abi import XBR_DEBUG_TOKEN_ADDR_SRC, XBR_DEBUG_NETWORK_ADDR_SRC, XBR_DEBUG_DOMAIN_ADDR_SRC, \
  39. XBR_DEBUG_CATALOG_ADDR_SRC, XBR_DEBUG_MARKET_ADDR_SRC, XBR_DEBUG_CHANNEL_ADDR_SRC
  40. from autobahn.xbr import FbsRepository
  41. import uuid
  42. import binascii
  43. import argparse
  44. import random
  45. from pprint import pformat
  46. import eth_keys
  47. import web3
  48. import hashlib
  49. import multihash
  50. import cbor2
  51. import numpy as np
  52. import txaio
  53. txaio.use_twisted()
  54. from twisted.internet import reactor
  55. from twisted.internet.defer import inlineCallbacks
  56. from twisted.internet.threads import deferToThread
  57. from twisted.internet.error import ReactorNotRunning
  58. from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner
  59. from autobahn.wamp.serializer import CBORSerializer
  60. from autobahn.wamp import cryptosign
  61. from autobahn.wamp.exception import ApplicationError
  62. from autobahn.xbr import pack_uint256, unpack_uint256, sign_eip712_channel_open, make_w3
  63. from autobahn.xbr import sign_eip712_member_register, sign_eip712_market_create, sign_eip712_market_join
  64. from autobahn.xbr import ActorType, ChannelType
  65. from autobahn.xbr._config import load_or_create_profile
  66. from autobahn.util import hltype, hlid, hlval
  67. _COMMANDS = ['version', 'get-member', 'register-member', 'register-member-verify',
  68. 'get-market', 'create-market', 'create-market-verify',
  69. 'get-actor', 'join-market', 'join-market-verify',
  70. 'get-channel', 'open-channel', 'close-channel',
  71. 'describe-schema', 'codegen-schema']
  72. class Client(ApplicationSession):
  73. # when running over TLS, require TLS channel binding
  74. CHANNEL_BINDING = 'tls-unique'
  75. def __init__(self, config=None):
  76. ApplicationSession.__init__(self, config)
  77. # FIXME
  78. self._default_gas = 100000
  79. self._chain_id = 4
  80. profile = config.extra.get('profile', None)
  81. if profile and profile.cskey:
  82. assert type(profile.cskey) == bytes and len(profile.cskey) == 32
  83. self._cskey_raw = profile.cskey
  84. self._key = cryptosign.CryptosignKey.from_bytes(self._cskey_raw)
  85. self.log.info('WAMP-Cryptosign keys with public key {public_key} loaded', public_key=self._key.public_key)
  86. else:
  87. self._cskey_raw = os.urandom(32)
  88. self._key = cryptosign.CryptosignKey.from_bytes(self._cskey_raw)
  89. self.log.info('WAMP-Cryptosign keys initialized randomly')
  90. if profile and profile.ethkey:
  91. self.set_ethkey_from_profile(profile)
  92. self.log.info('XBR ETH keys loaded from profile')
  93. else:
  94. self._ethkey_raw = None
  95. self._ethkey = None
  96. self._ethadr = None
  97. self._ethadr_raw = None
  98. self.log.info('XBR ETH keys left unset')
  99. self._running = True
  100. def set_ethkey_from_profile(self, profile):
  101. """
  102. :param profile:
  103. :return:
  104. """
  105. assert type(
  106. profile.ethkey) == bytes, 'set_ethkey_from_profile::profile invalid type "{}" - must be bytes'.format(
  107. type(profile.ethkey))
  108. assert len(profile.ethkey) == 32, 'set_ethkey_from_profile::profile invalid length {} - must be 32'.format(
  109. len(profile.ethkey))
  110. self._ethkey_raw = profile.ethkey
  111. self._ethkey = eth_keys.keys.PrivateKey(self._ethkey_raw)
  112. self._ethadr = web3.Web3.toChecksumAddress(self._ethkey.public_key.to_canonical_address())
  113. self._ethadr_raw = binascii.a2b_hex(self._ethadr[2:])
  114. self.log.info('ETH keys with address {ethadr} loaded', ethadr=self._ethadr)
  115. def onConnect(self):
  116. if self.config.realm == 'xbrnetwork':
  117. authextra = {
  118. 'pubkey': self._key.public_key(),
  119. 'trustroot': None,
  120. 'challenge': None,
  121. 'channel_binding': self.CHANNEL_BINDING,
  122. }
  123. self.log.info('Client connected, now joining realm "{realm}" with WAMP-cryptosign authentication ..',
  124. realm=hlid(self.config.realm))
  125. self.join(self.config.realm, authmethods=['cryptosign'], authextra=authextra)
  126. else:
  127. self.log.info('Client connected, now joining realm "{realm}" (no authentication) ..',
  128. realm=hlid(self.config.realm))
  129. self.join(self.config.realm)
  130. def onChallenge(self, challenge):
  131. if challenge.method == 'cryptosign':
  132. signed_challenge = self._key.sign_challenge(challenge,
  133. channel_id=self.transport.transport_details.channel_id.get(self.CHANNEL_BINDING, None),
  134. channel_id_type=self.CHANNEL_BINDING)
  135. return signed_challenge
  136. else:
  137. raise RuntimeError('unable to process authentication method {}'.format(challenge.method))
  138. async def onJoin(self, details):
  139. self.log.info(
  140. 'Ok, client joined on realm "{realm}" [session={session}, authid="{authid}", authrole="{authrole}"]',
  141. realm=hlid(details.realm),
  142. session=hlid(details.session),
  143. authid=hlid(details.authid),
  144. authrole=hlid(details.authrole),
  145. details=details)
  146. if 'ready' in self.config.extra:
  147. txaio.resolve(self.config.extra['ready'], (self, details))
  148. if 'command' in self.config.extra:
  149. try:
  150. if details.realm == 'xbrnetwork':
  151. await self._do_xbrnetwork_realm(details)
  152. else:
  153. await self._do_market_realm(details)
  154. except Exception as e:
  155. self.log.failure()
  156. self.config.extra['error'] = e
  157. finally:
  158. self.leave()
  159. def onLeave(self, details):
  160. self.log.info('Client left realm (reason="{reason}")', reason=hlval(details.reason))
  161. self._running = False
  162. if details.reason == 'wamp.close.normal':
  163. if self.config and self.config.runner:
  164. # user initiated leave => end the program
  165. self.config.runner.stop()
  166. self.disconnect()
  167. def onDisconnect(self):
  168. self.log.info('Client disconnected')
  169. try:
  170. reactor.stop()
  171. except ReactorNotRunning:
  172. pass
  173. async def _do_xbrnetwork_realm(self, details):
  174. command = self.config.extra['command']
  175. if details.authrole == 'anonymous':
  176. self.log.info('not yet a member in the XBR network')
  177. assert command in ['get-member', 'register-member', 'register-member-verify']
  178. if command == 'get-member':
  179. await self._do_get_member(self._ethadr_raw)
  180. elif command == 'register-member':
  181. username = self.config.extra['username']
  182. email = self.config.extra['email']
  183. await self._do_onboard_member(username, email)
  184. elif command == 'register-member-verify':
  185. vaction_oid = self.config.extra['vaction']
  186. vaction_code = self.config.extra['vcode']
  187. await self._do_onboard_member_verify(vaction_oid, vaction_code)
  188. else:
  189. assert False, 'should not arrive here'
  190. else:
  191. # WAMP authid on xbrnetwork follows this format: "member-"
  192. member_oid = uuid.UUID(details.authid[7:])
  193. # member_data = await self.call('xbr.network.get_member', member_oid.bytes)
  194. # self.log.info('Address is already a member in the XBR network:\n\n{member_data}', member_data=pformat(member_data))
  195. assert command in ['get-member', 'get-market', 'create-market', 'create-market-verify',
  196. 'get-actor', 'join-market', 'join-market-verify']
  197. if command == 'get-member':
  198. await self._do_get_member(self._ethadr_raw)
  199. elif command == 'get-market':
  200. market_oid = self.config.extra['market']
  201. await self._do_get_market(member_oid, market_oid)
  202. elif command == 'get-actor':
  203. if 'market' in self.config.extra and self.config.extra['market']:
  204. market_oid = self.config.extra['market']
  205. else:
  206. market_oid = None
  207. if 'actor' in self.config.extra and self.config.extra['actor']:
  208. actor = self.config.extra['actor']
  209. else:
  210. actor = self._ethadr_raw
  211. await self._do_get_actor(market_oid, actor)
  212. elif command == 'create-market':
  213. market_oid = self.config.extra['market']
  214. marketmaker = self.config.extra['marketmaker']
  215. market_title = self.config.extra['market_title']
  216. market_label = self.config.extra['market_label']
  217. market_homepage = self.config.extra['market_homepage']
  218. provider_security = self.config.extra['market_provider_security']
  219. consumer_security = self.config.extra['market_consumer_security']
  220. market_fee = self.config.extra['market_fee']
  221. await self._do_create_market(member_oid, market_oid, marketmaker, title=market_title,
  222. label=market_label, homepage=market_homepage,
  223. provider_security=provider_security, consumer_security=consumer_security,
  224. market_fee=market_fee)
  225. elif command == 'create-market-verify':
  226. vaction_oid = self.config.extra['vaction']
  227. vaction_code = self.config.extra['vcode']
  228. await self._do_create_market_verify(member_oid, vaction_oid, vaction_code)
  229. elif command == 'join-market':
  230. market_oid = self.config.extra['market']
  231. actor_type = self.config.extra['actor_type']
  232. await self._do_join_market(member_oid, market_oid, actor_type)
  233. elif command == 'join-market-verify':
  234. vaction_oid = self.config.extra['vaction']
  235. vaction_code = self.config.extra['vcode']
  236. await self._do_join_market_verify(member_oid, vaction_oid, vaction_code)
  237. else:
  238. assert False, 'should not arrive here'
  239. async def _do_market_realm(self, details):
  240. profile = self.config.extra['profile']
  241. blockchain_gateway = {
  242. "type": "infura",
  243. "network": profile.infura_network,
  244. "key": profile.infura_key,
  245. "secret": profile.infura_secret
  246. }
  247. self._w3 = make_w3(blockchain_gateway)
  248. xbr.setProvider(self._w3)
  249. command = self.config.extra['command']
  250. assert command in ['open-channel', 'get-channel']
  251. if command == 'get-channel':
  252. market_oid = self.config.extra['market']
  253. delegate = self.config.extra['delegate']
  254. channel_type = self.config.extra['channel_type']
  255. if channel_type == ChannelType.PAYMENT:
  256. await self._do_get_active_payment_channel(market_oid, delegate)
  257. elif channel_type == ChannelType.PAYING:
  258. await self._do_get_active_paying_channel(market_oid, delegate)
  259. else:
  260. assert False, 'should not arrive here'
  261. elif command == 'open-channel':
  262. # market in which to open the new buyer/seller (payment/paying) channel
  263. market_oid = self.config.extra['market']
  264. # read UUID of the new channel to be created from command line OR auto-generate a new one
  265. channel_oid = self.config.extra['channel'] or uuid.uuid4()
  266. # buyer/seller (payment/paying) channel
  267. channel_type = self.config.extra['channel_type']
  268. # the delgate allowed to use the channel
  269. delegate = self.config.extra['delegate']
  270. # amount of market coins for initial channel balance
  271. amount = self.config.extra['amount']
  272. # now open the channel ..
  273. await self._do_open_channel(market_oid, channel_oid, channel_type, delegate, amount)
  274. else:
  275. assert False, 'should not arrive here'
  276. async def _do_get_member(self, member_adr):
  277. is_member = await self.call('xbr.network.is_member', member_adr)
  278. if is_member:
  279. member_data = await self.call('xbr.network.get_member_by_wallet', member_adr)
  280. member_data['address'] = web3.Web3.toChecksumAddress(member_data['address'])
  281. member_data['oid'] = uuid.UUID(bytes=member_data['oid'])
  282. member_data['balance']['eth'] = web3.Web3.fromWei(unpack_uint256(member_data['balance']['eth']), 'ether')
  283. member_data['balance']['xbr'] = web3.Web3.fromWei(unpack_uint256(member_data['balance']['xbr']), 'ether')
  284. member_data['created'] = np.datetime64(member_data['created'], 'ns')
  285. member_level = member_data['level']
  286. MEMBER_LEVEL_TO_STR = {
  287. # Member is active.
  288. 1: 'ACTIVE',
  289. # Member is active and verified.
  290. 2: 'VERIFIED',
  291. # Member is retired.
  292. 3: 'RETIRED',
  293. # Member is subject to a temporary penalty.
  294. 4: 'PENALTY',
  295. # Member is currently blocked and cannot current actively participate in the market.
  296. 5: 'BLOCKED',
  297. }
  298. member_data['level'] = MEMBER_LEVEL_TO_STR.get(member_level, None)
  299. self.log.info('Member {member_oid} found for address 0x{member_adr} - current member level {member_level}',
  300. member_level=hlval(member_data['level']),
  301. member_oid=hlid(member_data['oid']),
  302. member_adr=hlval(member_data['address']))
  303. return member_data
  304. else:
  305. self.log.warn('Address 0x{member_adr} is not a member in the XBR network',
  306. member_adr=hlval(binascii.b2a_hex(member_adr).decode()))
  307. async def _do_get_actor(self, market_oid, actor_adr):
  308. is_member = await self.call('xbr.network.is_member', actor_adr)
  309. if is_member:
  310. actor = await self.call('xbr.network.get_member_by_wallet', actor_adr)
  311. actor_oid = uuid.UUID(bytes=actor['oid'])
  312. actor_adr = web3.Web3.toChecksumAddress(actor['address'])
  313. actor_level = actor['level']
  314. actor_balance_eth = web3.Web3.fromWei(unpack_uint256(actor['balance']['eth']), 'ether')
  315. actor_balance_xbr = web3.Web3.fromWei(unpack_uint256(actor['balance']['xbr']), 'ether')
  316. self.log.info(
  317. 'Found member with address {member_adr} (member level {member_level}, balances: {member_balance_eth} ETH, {member_balance_xbr} XBR)',
  318. member_adr=hlid(actor_adr),
  319. member_level=hlval(actor_level),
  320. member_balance_eth=hlval(actor_balance_eth),
  321. member_balance_xbr=hlval(actor_balance_xbr))
  322. if market_oid:
  323. market_oids = [market_oid.bytes]
  324. else:
  325. market_oids = await self.call('xbr.network.get_markets_by_actor', actor_oid.bytes)
  326. if market_oids:
  327. for market_oid in market_oids:
  328. # market = await self.call('xbr.network.get_market', market_oid)
  329. result = await self.call('xbr.network.get_actor_in_market', market_oid, actor['address'])
  330. for actor in result:
  331. actor['actor'] = web3.Web3.toChecksumAddress(actor['actor'])
  332. actor['timestamp'] = np.datetime64(actor['timestamp'], 'ns')
  333. actor['joined'] = unpack_uint256(actor['joined']) if actor['joined'] else None
  334. actor['market'] = uuid.UUID(bytes=actor['market'])
  335. actor['security'] = web3.Web3.fromWei(unpack_uint256(actor['security']), 'ether') if actor[
  336. 'security'] else None
  337. actor['signature'] = '0x' + binascii.b2a_hex(actor['signature']).decode() if actor[
  338. 'signature'] else None
  339. actor['tid'] = '0x' + binascii.b2a_hex(actor['tid']).decode() if actor['tid'] else None
  340. actor_type = actor['actor_type']
  341. ACTOR_TYPE_TO_STR = {
  342. # Actor is a XBR Provider.
  343. 1: 'PROVIDER',
  344. # Actor is a XBR Consumer.
  345. 2: 'CONSUMER',
  346. # Actor is both a XBR Provider and XBR Consumer.
  347. 3: 'PROVIDER_CONSUMER',
  348. }
  349. actor['actor_type'] = ACTOR_TYPE_TO_STR.get(actor_type, None)
  350. self.log.info('Actor is joined to market {market_oid}:\n\n{actor}\n',
  351. market_oid=hlid(uuid.UUID(bytes=market_oid)), actor=pformat(actor))
  352. else:
  353. self.log.info('Member is not yet actor in any market!')
  354. else:
  355. self.log.warn('Address 0x{member_adr} is not a member in the XBR network',
  356. member_adr=binascii.b2a_hex(actor_adr).decode())
  357. @inlineCallbacks
  358. def _do_onboard_member(self, member_username, member_email, member_password=None):
  359. client_pubkey = binascii.a2b_hex(self._key.public_key())
  360. # fake wallet type "metamask"
  361. wallet_type = 'metamask'
  362. # delegate ethereum private key object
  363. wallet_key = self._ethkey
  364. wallet_raw = self._ethkey_raw
  365. # delegate ethereum account canonical address
  366. wallet_adr = wallet_key.public_key.to_canonical_address()
  367. config = yield self.call('xbr.network.get_config')
  368. status = yield self.call('xbr.network.get_status')
  369. verifyingChain = config['verifying_chain_id']
  370. verifyingContract = binascii.a2b_hex(config['verifying_contract_adr'][2:])
  371. registered = status['block']['number']
  372. eula = config['eula']['hash']
  373. # create an aux-data object with info only stored off-chain (in our xbrbackend DB) ..
  374. profile_obj = {
  375. 'member_username': member_username,
  376. 'member_email': member_email,
  377. 'client_pubkey': client_pubkey,
  378. 'wallet_type': wallet_type,
  379. }
  380. # .. hash the serialized aux-data object ..
  381. profile_data = cbor2.dumps(profile_obj)
  382. h = hashlib.sha256()
  383. h.update(profile_data)
  384. # .. compute the sha256 multihash b58-encoded string from that ..
  385. profile = multihash.to_b58_string(multihash.encode(h.digest(), 'sha2-256'))
  386. signature = sign_eip712_member_register(wallet_raw, verifyingChain, verifyingContract,
  387. wallet_adr, registered, eula, profile)
  388. # https://xbr.network/docs/network/api.html#xbrnetwork.XbrNetworkApi.onboard_member
  389. try:
  390. result = yield self.call('xbr.network.onboard_member',
  391. member_username, member_email, client_pubkey, wallet_type, wallet_adr,
  392. verifyingChain, registered, verifyingContract, eula, profile, profile_data,
  393. signature)
  394. except ApplicationError as e:
  395. self.log.error('ApplicationError: {error}', error=e)
  396. self.leave('wamp.error', str(e))
  397. return
  398. except Exception as e:
  399. raise e
  400. assert type(result) == dict
  401. assert 'timestamp' in result and type(result['timestamp']) == int and result['timestamp'] > 0
  402. assert 'action' in result and result['action'] == 'onboard_member'
  403. assert 'vaction_oid' in result and type(result['vaction_oid']) == bytes and len(result['vaction_oid']) == 16
  404. vaction_oid = uuid.UUID(bytes=result['vaction_oid'])
  405. self.log.info('On-boarding member - verification "{vaction_oid}" created', vaction_oid=vaction_oid)
  406. return result
  407. @inlineCallbacks
  408. def _do_onboard_member_verify(self, vaction_oid, vaction_code):
  409. self.log.info('Verifying member using vaction_oid={vaction_oid}, vaction_code={vaction_code} ..',
  410. vaction_oid=vaction_oid, vaction_code=vaction_code)
  411. try:
  412. result = yield self.call('xbr.network.verify_onboard_member', vaction_oid.bytes, vaction_code)
  413. except ApplicationError as e:
  414. self.log.error('ApplicationError: {error}', error=e)
  415. raise e
  416. assert type(result) == dict
  417. assert 'member_oid' in result and type(result['member_oid']) == bytes and len(result['member_oid']) == 16
  418. assert 'created' in result and type(result['created']) == int and result['created'] > 0
  419. member_oid = result['member_oid']
  420. self.log.info('SUCCESS! New XBR Member onboarded: member_oid={member_oid}, transaction={transaction}',
  421. member_oid=hlid(uuid.UUID(bytes=member_oid)),
  422. transaction=hlval('0x' + binascii.b2a_hex(result['transaction']).decode()))
  423. return result
  424. async def _do_create_market(self, member_oid, market_oid, marketmaker, title=None, label=None, homepage=None,
  425. provider_security=0, consumer_security=0, market_fee=0):
  426. member_data = await self.call('xbr.network.get_member', member_oid.bytes)
  427. member_adr = member_data['address']
  428. config = await self.call('xbr.network.get_config')
  429. verifyingChain = config['verifying_chain_id']
  430. verifyingContract = binascii.a2b_hex(config['verifying_contract_adr'][2:])
  431. coin_adr = binascii.a2b_hex(config['contracts']['xbrtoken'][2:])
  432. status = await self.call('xbr.network.get_status')
  433. block_number = status['block']['number']
  434. # count all markets before we create a new one:
  435. res = await self.call('xbr.network.find_markets')
  436. cnt_market_before = len(res)
  437. self.log.info('Total markets before: {cnt_market_before}', cnt_market_before=cnt_market_before)
  438. res = await self.call('xbr.network.get_markets_by_owner', member_oid.bytes)
  439. cnt_market_by_owner_before = len(res)
  440. self.log.info('Market for owner: {cnt_market_by_owner_before}',
  441. cnt_market_by_owner_before=cnt_market_by_owner_before)
  442. # collect information for market creation that is stored on-chain
  443. # terms text: encode in utf8 and compute BIP58 multihash string
  444. terms_data = 'these are my market terms (randint={})'.format(random.randint(0, 1000)).encode('utf8')
  445. h = hashlib.sha256()
  446. h.update(terms_data)
  447. terms_hash = str(multihash.to_b58_string(multihash.encode(h.digest(), 'sha2-256')))
  448. # market meta data that doesn't change. the hash of this is part of the data that is signed and also
  449. # stored on-chain (only the hash, not the meta data!)
  450. meta_obj = {
  451. 'chain_id': verifyingChain,
  452. 'block_number': block_number,
  453. 'contract_adr': verifyingContract,
  454. 'member_adr': member_adr,
  455. 'member_oid': member_oid.bytes,
  456. 'market_oid': market_oid.bytes,
  457. }
  458. meta_data = cbor2.dumps(meta_obj)
  459. h = hashlib.sha256()
  460. h.update(meta_data)
  461. meta_hash = multihash.to_b58_string(multihash.encode(h.digest(), 'sha2-256'))
  462. # create signature for pre-signed transaction
  463. signature = sign_eip712_market_create(self._ethkey_raw, verifyingChain, verifyingContract, member_adr,
  464. block_number, market_oid.bytes, coin_adr, terms_hash, meta_hash,
  465. marketmaker, provider_security, consumer_security, market_fee)
  466. # for wire transfer, convert to bytes
  467. provider_security = pack_uint256(provider_security)
  468. consumer_security = pack_uint256(consumer_security)
  469. market_fee = pack_uint256(market_fee)
  470. # market settings that can change. even though changing might require signing, neither the data nor
  471. # and signatures are stored on-chain. however, even when only signed off-chain, this establishes
  472. # a chain of signature anchored in the on-chain record for this market!
  473. attributes = {
  474. 'title': title,
  475. 'label': label,
  476. 'homepage': homepage,
  477. }
  478. # now provide everything of above:
  479. # - market operator (owning member) and market oid
  480. # - signed market data and signature
  481. # - settings
  482. createmarket_request_submitted = await self.call('xbr.network.create_market', member_oid.bytes,
  483. market_oid.bytes, verifyingChain, block_number,
  484. verifyingContract, coin_adr, terms_hash, meta_hash, meta_data,
  485. marketmaker, provider_security, consumer_security, market_fee,
  486. signature, attributes)
  487. self.log.info('SUCCESS: Create market request submitted: \n{createmarket_request_submitted}\n',
  488. createmarket_request_submitted=pformat(createmarket_request_submitted))
  489. assert type(createmarket_request_submitted) == dict
  490. assert 'timestamp' in createmarket_request_submitted and type(
  491. createmarket_request_submitted['timestamp']) == int and createmarket_request_submitted['timestamp'] > 0
  492. assert 'action' in createmarket_request_submitted and createmarket_request_submitted[
  493. 'action'] == 'create_market'
  494. assert 'vaction_oid' in createmarket_request_submitted and type(
  495. createmarket_request_submitted['vaction_oid']) == bytes and len(
  496. createmarket_request_submitted['vaction_oid']) == 16
  497. vaction_oid = uuid.UUID(bytes=createmarket_request_submitted['vaction_oid'])
  498. self.log.info('SUCCESS: New Market verification "{vaction_oid}" created', vaction_oid=vaction_oid)
  499. async def _do_create_market_verify(self, member_oid, vaction_oid, vaction_code):
  500. self.log.info('Verifying create market using vaction_oid={vaction_oid}, vaction_code={vaction_code} ..',
  501. vaction_oid=vaction_oid, vaction_code=vaction_code)
  502. create_market_request_verified = await self.call('xbr.network.verify_create_market', vaction_oid.bytes,
  503. vaction_code)
  504. self.log.info('Create market request verified: \n{create_market_request_verified}\n',
  505. create_market_request_verified=pformat(create_market_request_verified))
  506. assert type(create_market_request_verified) == dict
  507. assert 'market_oid' in create_market_request_verified and type(
  508. create_market_request_verified['market_oid']) == bytes and len(
  509. create_market_request_verified['market_oid']) == 16
  510. assert 'created' in create_market_request_verified and type(
  511. create_market_request_verified['created']) == int and create_market_request_verified['created'] > 0
  512. market_oid = create_market_request_verified['market_oid']
  513. self.log.info('SUCCESS! New XBR market created: market_oid={market_oid}, result=\n{result}',
  514. market_oid=uuid.UUID(bytes=market_oid), result=pformat(create_market_request_verified))
  515. market_oids = await self.call('xbr.network.find_markets')
  516. self.log.info('SUCCESS - find_markets: found {cnt_markets} markets', cnt_markets=len(market_oids))
  517. # count all markets after we created a new market:
  518. cnt_market_after = len(market_oids)
  519. self.log.info('Total markets after: {cnt_market_after}', cnt_market_after=cnt_market_after)
  520. assert market_oid in market_oids, 'expected to find market ID {}, but not found in {} returned market IDs'.format(
  521. uuid.UUID(bytes=market_oid), len(market_oids))
  522. market_oids = await self.call('xbr.network.get_markets_by_owner', member_oid.bytes)
  523. self.log.info('SUCCESS - get_markets_by_owner: found {cnt_markets} markets', cnt_markets=len(market_oids))
  524. # count all markets after we created a new market:
  525. cnt_market_by_owner_after = len(market_oids)
  526. self.log.info('Market for owner: {cnt_market_by_owner_after}',
  527. cnt_market_by_owner_before=cnt_market_by_owner_after)
  528. assert market_oid in market_oids, 'expected to find market ID {}, but not found in {} returned market IDs'.format(
  529. uuid.UUID(bytes=market_oid), len(market_oids))
  530. for market_oid in market_oids:
  531. self.log.info('xbr.network.get_market(market_oid={market_oid}) ..', market_oid=market_oid)
  532. market = await self.call('xbr.network.get_market', market_oid, include_attributes=True)
  533. self.log.info('SUCCESS: got market information\n\n{market}\n', market=pformat(market))
  534. async def _do_get_market(self, member_oid, market_oid):
  535. member_data = await self.call('xbr.network.get_member', member_oid.bytes)
  536. member_adr = member_data['address']
  537. market = await self.call('xbr.network.get_market', market_oid.bytes)
  538. if market:
  539. if market['owner'] == member_adr:
  540. self.log.info('You are market owner (operator)!')
  541. else:
  542. self.log.info('Marked is owned by {owner}', owner=hlid(web3.Web3.toChecksumAddress(market['owner'])))
  543. market['market'] = uuid.UUID(bytes=market['market'])
  544. market['owner'] = web3.Web3.toChecksumAddress(market['owner'])
  545. market['maker'] = web3.Web3.toChecksumAddress(market['maker'])
  546. market['coin'] = web3.Web3.toChecksumAddress(market['coin'])
  547. market['timestamp'] = np.datetime64(market['timestamp'], 'ns')
  548. self.log.info('Market {market_oid} information:\n\n{market}\n',
  549. market_oid=hlid(market_oid), market=pformat(market))
  550. else:
  551. self.log.warn('No market {market_oid} found!', market_oid=hlid(market_oid))
  552. async def _do_join_market(self, member_oid, market_oid, actor_type):
  553. assert actor_type in [ActorType.CONSUMER, ActorType.PROVIDER, ActorType.PROVIDER_CONSUMER]
  554. member_data = await self.call('xbr.network.get_member', member_oid.bytes)
  555. member_adr = member_data['address']
  556. config = await self.call('xbr.network.get_config')
  557. verifyingChain = config['verifying_chain_id']
  558. verifyingContract = binascii.a2b_hex(config['verifying_contract_adr'][2:])
  559. status = await self.call('xbr.network.get_status')
  560. block_number = status['block']['number']
  561. meta_obj = {
  562. }
  563. meta_data = cbor2.dumps(meta_obj)
  564. h = hashlib.sha256()
  565. h.update(meta_data)
  566. meta_hash = multihash.to_b58_string(multihash.encode(h.digest(), 'sha2-256'))
  567. signature = sign_eip712_market_join(self._ethkey_raw, verifyingChain, verifyingContract, member_adr,
  568. block_number, market_oid.bytes, actor_type, meta_hash)
  569. request_submitted = await self.call('xbr.network.join_market', member_oid.bytes, market_oid.bytes,
  570. verifyingChain, block_number, verifyingContract,
  571. actor_type, meta_hash, meta_data, signature)
  572. vaction_oid = uuid.UUID(bytes=request_submitted['vaction_oid'])
  573. self.log.info('SUCCESS! XBR market join request submitted: vaction_oid={vaction_oid}', vaction_oid=vaction_oid)
  574. async def _do_join_market_verify(self, member_oid, vaction_oid, vaction_code):
  575. request_verified = await self.call('xbr.network.verify_join_market', vaction_oid.bytes, vaction_code)
  576. market_oid = request_verified['market_oid']
  577. actor_type = request_verified['actor_type']
  578. self.log.info(
  579. 'SUCCESS! XBR market joined: member_oid={member_oid}, market_oid={market_oid}, actor_type={actor_type}',
  580. member_oid=member_oid, market_oid=market_oid, actor_type=actor_type)
  581. async def _do_get_active_payment_channel(self, market_oid, delegate_adr):
  582. channel = await self.call('xbr.marketmaker.get_active_payment_channel', delegate_adr)
  583. self.log.debug('{channel}', channel=pformat(channel))
  584. if channel:
  585. self.log.info('Active buyer (payment) channel found: {amount} amount',
  586. amount=int(unpack_uint256(channel['amount']) / 10 ** 18))
  587. balance = await self.call('xbr.marketmaker.get_payment_channel_balance', channel['channel_oid'])
  588. self.log.debug('{balance}', channel=pformat(balance))
  589. self.log.info('Current off-chain amount remaining: {remaining} [sequence {sequence}]',
  590. remaining=int(unpack_uint256(balance['remaining']) / 10 ** 18), sequence=balance['seq'])
  591. else:
  592. self.log.info('No active buyer (payment) channel found!')
  593. async def _do_get_active_paying_channel(self, market_oid, delegate_adr):
  594. channel = await self.call('xbr.marketmaker.get_active_paying_channel', delegate_adr)
  595. self.log.debug('{channel}', channel=pformat(channel))
  596. if channel:
  597. self.log.info('Active seller (paying) channel found: {amount} amount',
  598. amount=int(unpack_uint256(channel['amount']) / 10 ** 18))
  599. balance = await self.call('xbr.marketmaker.get_paying_channel_balance', channel['channel_oid'])
  600. self.log.debug('{balance}', channel=pformat(balance))
  601. self.log.info('Current off-chain amount remaining: {remaining} [sequence {sequence}]',
  602. remaining=int(unpack_uint256(balance['remaining']) / 10 ** 18), sequence=balance['seq'])
  603. else:
  604. self.log.info('No active seller (paying) channel found!')
  605. async def _do_open_channel(self, market_oid, channel_oid, channel_type, delegate, amount):
  606. member_key = self._ethkey_raw
  607. member_adr = self._ethkey.public_key.to_canonical_address()
  608. config = await self.call('xbr.marketmaker.get_config')
  609. marketmaker = binascii.a2b_hex(config['marketmaker'][2:])
  610. recipient = binascii.a2b_hex(config['owner'][2:])
  611. verifying_chain_id = config['verifying_chain_id']
  612. verifying_contract_adr = binascii.a2b_hex(config['verifying_contract_adr'][2:])
  613. status = await self.call('xbr.marketmaker.get_status')
  614. current_block_number = status['block']['number']
  615. if amount > 0:
  616. if channel_type == ChannelType.PAYMENT:
  617. from_adr = member_adr
  618. to_adr = xbr.xbrchannel.address
  619. elif channel_type == ChannelType.PAYING:
  620. from_adr = marketmaker
  621. to_adr = xbr.xbrchannel.address
  622. else:
  623. assert False, 'should not arrive here'
  624. # allowance1 = xbr.xbrtoken.functions.allowance(transact_from, xbr.xbrchannel.address).call()
  625. # xbr.xbrtoken.functions.approve(to_adr, amount).transact(
  626. # {'from': transact_from, 'gas': transact_gas})
  627. # allowance2 = xbr.xbrtoken.functions.allowance(transact_from, xbr.xbrchannel.address).call()
  628. # assert allowance2 - allowance1 == amount
  629. try:
  630. txn_hash = await deferToThread(self._send_Allowance, from_adr, to_adr, amount)
  631. self.log.info('transaction submitted, txn_hash={txn_hash}', txn_hash=txn_hash)
  632. except Exception as e:
  633. self.log.failure()
  634. raise e
  635. # compute EIP712 signature, and sign using member private key
  636. signature = sign_eip712_channel_open(member_key, verifying_chain_id, verifying_contract_adr, channel_type,
  637. current_block_number, market_oid.bytes, channel_oid.bytes,
  638. member_adr, delegate, marketmaker, recipient, amount)
  639. attributes = None
  640. channel_request = await self.call('xbr.marketmaker.open_channel', member_adr, market_oid.bytes,
  641. channel_oid.bytes, verifying_chain_id, current_block_number,
  642. verifying_contract_adr, channel_type, delegate, marketmaker, recipient,
  643. pack_uint256(amount), signature, attributes)
  644. self.log.info('Channel open request submitted:\n\n{channel_request}\n',
  645. channel_request=pformat(channel_request))
  646. def _send_Allowance(self, from_adr, to_adr, amount):
  647. # FIXME: estimate gas required for call
  648. gas = self._default_gas
  649. gasPrice = self._w3.toWei('10', 'gwei')
  650. from_adr = self._ethadr
  651. # each submitted transaction must contain a nonce, which is obtained by the on-chain transaction number
  652. # for this account, including pending transactions (I think ..;) ..
  653. nonce = self._w3.eth.getTransactionCount(from_adr, block_identifier='pending')
  654. self.log.info('{func}::[1/4] - Ethereum transaction nonce: nonce={nonce}',
  655. func=hltype(self._send_Allowance),
  656. nonce=nonce)
  657. # serialize transaction raw data from contract call and transaction settings
  658. raw_transaction = xbr.xbrtoken.functions.approve(to_adr, amount).buildTransaction({
  659. 'from': from_adr,
  660. 'gas': gas,
  661. 'gasPrice': gasPrice,
  662. 'chainId': self._chain_id, # https://stackoverflow.com/a/57901206/884770
  663. 'nonce': nonce,
  664. })
  665. self.log.info(
  666. '{func}::[2/4] - Ethereum transaction created: raw_transaction=\n{raw_transaction}\n',
  667. func=hltype(self._send_Allowance),
  668. raw_transaction=raw_transaction)
  669. # compute signed transaction from above serialized raw transaction
  670. signed_txn = self._w3.eth.account.sign_transaction(raw_transaction, private_key=self._ethkey_raw)
  671. self.log.info(
  672. '{func}::[3/4] - Ethereum transaction signed: signed_txn=\n{signed_txn}\n',
  673. func=hltype(self._send_Allowance),
  674. signed_txn=hlval(binascii.b2a_hex(signed_txn.rawTransaction).decode()))
  675. # now send the pre-signed transaction to the blockchain via the gateway ..
  676. # https://web3py.readthedocs.io/en/stable/web3.eth.html # web3.eth.Eth.sendRawTransaction
  677. txn_hash = self._w3.eth.sendRawTransaction(signed_txn.rawTransaction)
  678. txn_hash = bytes(txn_hash)
  679. self.log.info(
  680. '{func}::[4/4] - Ethereum transaction submitted: txn_hash=0x{txn_hash}',
  681. func=hltype(self._send_Allowance),
  682. txn_hash=hlval(binascii.b2a_hex(txn_hash).decode()))
  683. return txn_hash
  684. def print_version():
  685. print('')
  686. print(' XBR CLI {}\n'.format(hlval('v' + __version__)))
  687. print('')
  688. print(' Contract addresses:\n')
  689. print(' XBRToken : {} [source: {}]'.format(hlid(XBR_DEBUG_TOKEN_ADDR), XBR_DEBUG_TOKEN_ADDR_SRC))
  690. print(' XBRNetwork : {} [source: {}]'.format(hlid(XBR_DEBUG_NETWORK_ADDR), XBR_DEBUG_NETWORK_ADDR_SRC))
  691. print(' XBRDomain : {} [source: {}]'.format(hlid(XBR_DEBUG_DOMAIN_ADDR), XBR_DEBUG_DOMAIN_ADDR_SRC))
  692. print(' XBRCatalog : {} [source: {}]'.format(hlid(XBR_DEBUG_CATALOG_ADDR), XBR_DEBUG_CATALOG_ADDR_SRC))
  693. print(' XBRMarket : {} [source: {}]'.format(hlid(XBR_DEBUG_MARKET_ADDR), XBR_DEBUG_MARKET_ADDR_SRC))
  694. print(' XBRChannel : {} [source: {}]'.format(hlid(XBR_DEBUG_CHANNEL_ADDR), XBR_DEBUG_CHANNEL_ADDR_SRC))
  695. print('')
  696. def _main():
  697. parser = argparse.ArgumentParser()
  698. parser.add_argument('command',
  699. type=str,
  700. choices=_COMMANDS,
  701. const='noop',
  702. nargs='?',
  703. help='Command to run')
  704. parser.add_argument('-d',
  705. '--debug',
  706. action='store_true',
  707. help='Enable debug output.')
  708. parser.add_argument('-o',
  709. '--output',
  710. type=str,
  711. help='Code output folder')
  712. parser.add_argument('-s',
  713. '--schema',
  714. dest='schema',
  715. type=str,
  716. help='FlatBuffers binary schema file to read (.bfbs)')
  717. parser.add_argument('-b',
  718. '--basemodule',
  719. dest='basemodule',
  720. type=str,
  721. help='Render to this base module')
  722. _LANGUAGES = ['python', 'json']
  723. parser.add_argument('-l',
  724. '--language',
  725. dest='language',
  726. type=str,
  727. help='Generated code language, one of {}'.format(_LANGUAGES))
  728. parser.add_argument('--url',
  729. dest='url',
  730. type=str,
  731. default='wss://planet.xbr.network/ws',
  732. help='The router URL (default: "wss://planet.xbr.network/ws").')
  733. parser.add_argument('--realm',
  734. dest='realm',
  735. type=str,
  736. default='xbrnetwork',
  737. help='The realm to join (default: "xbrnetwork").')
  738. parser.add_argument('--ethkey',
  739. dest='ethkey',
  740. type=str,
  741. help='Private Ethereum key (32 bytes as HEX encoded string)')
  742. parser.add_argument('--cskey',
  743. dest='cskey',
  744. type=str,
  745. help='Private WAMP-cryptosign authentication key (32 bytes as HEX encoded string)')
  746. parser.add_argument('--username',
  747. dest='username',
  748. type=str,
  749. default=None,
  750. help='For on-boarding, the new member username.')
  751. parser.add_argument('--email',
  752. dest='email',
  753. type=str,
  754. default=None,
  755. help='For on-boarding, the new member email address.')
  756. parser.add_argument('--market',
  757. dest='market',
  758. type=str,
  759. default=None,
  760. help='For creating new markets, the market UUID.')
  761. parser.add_argument('--market_title',
  762. dest='market_title',
  763. type=str,
  764. default=None,
  765. help='For creating new markets, the market title.')
  766. parser.add_argument('--market_label',
  767. dest='market_label',
  768. type=str,
  769. default=None,
  770. help='For creating new markets, the market label.')
  771. parser.add_argument('--market_homepage',
  772. dest='market_homepage',
  773. type=str,
  774. default=None,
  775. help='For creating new markets, the market homepage.')
  776. parser.add_argument('--provider_security',
  777. dest='provider_security',
  778. type=int,
  779. default=None,
  780. help='')
  781. parser.add_argument('--consumer_security',
  782. dest='consumer_security',
  783. type=int,
  784. default=None,
  785. help='')
  786. parser.add_argument('--market_fee',
  787. dest='market_fee',
  788. type=int,
  789. default=None,
  790. help='')
  791. parser.add_argument('--marketmaker',
  792. dest='marketmaker',
  793. type=str,
  794. default=None,
  795. help='For creating new markets, the market maker address.')
  796. parser.add_argument('--actor_type',
  797. dest='actor_type',
  798. type=int,
  799. choices=sorted([ActorType.CONSUMER, ActorType.PROVIDER, ActorType.PROVIDER_CONSUMER]),
  800. default=None,
  801. help='Actor type: PROVIDER = 1, CONSUMER = 2, PROVIDER_CONSUMER (both) = 3')
  802. parser.add_argument('--vcode',
  803. dest='vcode',
  804. type=str,
  805. default=None,
  806. help='For verifications of actions, the verification UUID.')
  807. parser.add_argument('--vaction',
  808. dest='vaction',
  809. type=str,
  810. default=None,
  811. help='For verifications of actions (on-board, create-market, ..), the verification code.')
  812. parser.add_argument('--channel',
  813. dest='channel',
  814. type=str,
  815. default=None,
  816. help='For creating new channel, the channel UUID.')
  817. parser.add_argument('--channel_type',
  818. dest='channel_type',
  819. type=int,
  820. choices=sorted([ChannelType.PAYING, ChannelType.PAYMENT]),
  821. default=None,
  822. help='Channel type: Seller (PAYING) = 1, Buyer (PAYMENT) = 2')
  823. parser.add_argument('--delegate',
  824. dest='delegate',
  825. type=str,
  826. default=None,
  827. help='For creating new channel, the delegate address.')
  828. parser.add_argument('--amount',
  829. dest='amount',
  830. type=int,
  831. default=None,
  832. help='Amount to open the channel with. In tokens of the market coin type, used as means of payment in the market of the channel.')
  833. args = parser.parse_args()
  834. if args.command == 'version':
  835. print_version()
  836. # describe schema in WAMP IDL FlatBuffers schema files
  837. elif args.command == 'describe-schema':
  838. repo = FbsRepository(basemodule=args.basemodule)
  839. repo.load(args.schema)
  840. total_count = len(repo.objs) + len(repo.enums) + len(repo.services)
  841. print('ok, loaded {} types ({} structs and tables, {} enums and {} service interfaces)'.format(
  842. hlval(total_count),
  843. hlval(len(repo.objs)),
  844. hlval(len(repo.enums)),
  845. hlval(len(repo.services))))
  846. print()
  847. repo.print_summary()
  848. # generate code from WAMP IDL FlatBuffers schema files
  849. elif args.command == 'codegen-schema':
  850. # load repository from flatbuffers schema files
  851. repo = FbsRepository(basemodule=args.basemodule)
  852. repo.load(args.schema)
  853. # print repository summary
  854. print(repo.summary(keys=True))
  855. # folder with jinja2 templates for python code sections
  856. templates = pkg_resources.resource_filename('autobahn', 'xbr/templates')
  857. # jinja2 template engine loader and environment
  858. loader = FileSystemLoader(templates, encoding='utf-8', followlinks=False)
  859. env = Environment(loader=loader, trim_blocks=True, lstrip_blocks=True)
  860. # output directory for generated code
  861. if not os.path.isdir(args.output):
  862. os.mkdir(args.output)
  863. # render python source code files
  864. repo.render(env, args.output, 'python')
  865. else:
  866. if args.command is None or args.command == 'noop':
  867. print('no command given. select from: {}'.format(', '.join(_COMMANDS)))
  868. sys.exit(0)
  869. # read or create a user profile
  870. profile = load_or_create_profile(default_url=args.url, default_realm=args.realm,
  871. default_username=args.username, default_email=args.email)
  872. # only start txaio logging after above, which runs click (interactively)
  873. if args.debug:
  874. txaio.start_logging(level='debug')
  875. else:
  876. txaio.start_logging(level='info')
  877. log = txaio.make_logger()
  878. log.info('XBR CLI {version}', version=hlid('v' + __version__))
  879. log.info('Profile {profile} loaded from {path}', profile=hlval(profile.name), path=hlval(profile.path))
  880. extra = {
  881. # user profile and defaults
  882. 'profile': profile,
  883. # allow to override, and add more arguments from the command line
  884. 'command': args.command,
  885. 'username': args.username,
  886. 'email': args.email,
  887. 'ethkey': profile.ethkey,
  888. 'cskey': profile.cskey,
  889. 'market': uuid.UUID(args.market) if args.market else None,
  890. 'market_title': args.market_title,
  891. 'market_label': args.market_label,
  892. 'market_homepage': args.market_homepage,
  893. 'market_provider_security': args.provider_security or 0,
  894. 'market_consumer_security': args.consumer_security or 0,
  895. 'market_fee': args.market_fee or 0,
  896. 'marketmaker': binascii.a2b_hex(args.marketmaker[2:]) if args.marketmaker else None,
  897. 'actor_type': args.actor_type,
  898. 'vcode': args.vcode,
  899. 'vaction': uuid.UUID(args.vaction) if args.vaction else None,
  900. 'channel': uuid.UUID(args.channel) if args.channel else None,
  901. 'channel_type': args.channel_type,
  902. 'delegate': binascii.a2b_hex(args.delegate[2:]) if args.delegate else None,
  903. 'amount': args.amount or 0,
  904. }
  905. runner = ApplicationRunner(url=profile.network_url, realm=profile.network_realm, extra=extra,
  906. serializers=[CBORSerializer()])
  907. try:
  908. log.info('Connecting to "{url}" {realm} ..',
  909. url=hlval(profile.network_url),
  910. realm=('at realm "' + hlval(profile.network_realm) + '"' if profile.network_realm else ''))
  911. runner.run(Client, auto_reconnect=False)
  912. except Exception as e:
  913. print(e)
  914. sys.exit(1)
  915. else:
  916. sys.exit(0)
  917. if __name__ == '__main__':
  918. _main()