def check_anything_deployed(address): from ethjsonrpc import EthJsonRpc c = EthJsonRpc(DEFAULT_RPC_HOST, DEFAULT_RPC_PORT) if c.eth_getCode(address) == '0x0': print('NOTHING DEPLOYED AT SPECIFIED ADDRESS:', address) return False return True
class ContractWrapper: def __init__( self, the_code=False, the_sig=None, the_args=None, the_address=False, events_callback=False, deploy_callback=False, blocking_sleep_time=0.1, rpc_host=DEFAULT_RPC_HOST, rpc_port=DEFAULT_RPC_PORT, settings_confirm_states={}, contract_address=False, start_at_current_block=False, auto_deploy=True, contract_thread_sleep_time=1.0, reorg_callback=False, ): """ Simple contract wrapper, assists with deploying contract, sending transactions, and tracking event logs. Args: - the_code: solidity code for contract that should be deployed, prior to any operations. - the_address: address of already-deployed main contract. - contract_address: contract address, from previous `deploy()` call. - the_sig: optional constructor signature. - the_args: optional constructor args. - events_callback: callback for event messages e.g. `TheLog()`, `MintEvent()`, `LockupTokEvent()`, `TransferTokEvent()`. - deploy_callback: callback for contract deploys. - blocking_sleep_time: time to sleep when blocking and polling for a transaction receipt. """ self.block_details = {} self.reorg_callback = reorg_callback self.confirmation_tracker = { } ## {'block_hash':{'prev_block_hash':xx, 'block_num':yy}} self.done_block_nums = {} ## {confirm_state:set()} self.done_transactions = {} ## {confirm_state:set()} self.prev_block_num = {} ## {confirm_state:set()} self.blocking_sleep_time = blocking_sleep_time self.c = EthJsonRpc(rpc_host, rpc_port) self.contract_thread_sleep_time = contract_thread_sleep_time self.start_at_current_block = start_at_current_block self.current_block_at_init = self.c.eth_blockNumber() if self.start_at_current_block: self.last_incoming_block = max(0, self.current_block_at_init - 1) else: self.last_incoming_block = 0 self.starting_block_num = self.last_incoming_block self.msgs = {} ## {block_num:[msg, msg, msg]} self.the_code = the_code self.the_sig = the_sig self.the_args = the_args self.contract_address = the_address assert self.the_code or self.contract_address self.loop_block_num = -1 self.confirm_states = settings_confirm_states self.events_callback = events_callback self.pending_transactions = {} ## {tx:callback} self.pending_logs = {} self.latest_block_num = -1 self.latest_block_num_done = 0 self.send_transaction_queue = Queue() self.is_deployed = False if auto_deploy: if the_address: assert self.check_anything_deployed(the_address), ( 'NOTHING DEPLOYED AT SPECIFIED ADDRESS:', the_address) self.is_deployed = True elif the_code: self.deploy() def check_anything_deployed(self, address): """ Basic sanity check, checks if ANY code is deployed at provided address. """ if self.c.eth_getCode(address) == '0x0': return False return True def deploy( self, the_sig=False, the_args=False, block=False, deploy_from=False, callback=False, ): """ Deploy contract. Optional args_sig and args used to pass arguments to contract constructor.""" if the_sig is not False: self.the_sig = the_sig if the_args is not False: self.the_args = the_args assert self.the_code if deploy_from is False: deploy_from = self.c.eth_coinbase() print('DEPLOYING_CONTRACT...', 'deploy_from:', deploy_from, 'the_sig:', the_sig, 'the_args:', the_args) # get contract address xx = self.c.eth_compileSolidity(self.the_code) #print ('GOT',xx) compiled = get_compiled_code(xx) contract_tx = self.c.create_contract( from_=deploy_from, code=compiled, gas=3000000, sig=self.the_sig, args=self.the_args, ) if block: ## NOTE: @yusef feel free to switch back to this method if you want: #print('CONTRACT DEPLOYED, WAITING FOR CONFIRMATION') #wait_for_confirmation(self.c, contract_tx) print('BLOCKING FOR RECEIPT..') while True: receipt = self.c.eth_getTransactionReceipt( contract_tx) ## blocks to ensure transaction is mined if receipt: break sleep(self.blocking_sleep_time) print('GOT RECEIPT') else: self.pending_transactions[contract_tx] = (callback, self.latest_block_num) self.contract_address = str(self.c.get_contract_address(contract_tx)) self.is_deployed = True print('DEPLOYED', self.contract_address) return self.contract_address def loop_once(self): assert self.is_deployed, 'Must deploy contract first.' had_any_events = False if self.c.eth_syncing(): print('BLOCKCHAIN_STILL_SYNCING') return False if self.events_callback is not False: had_any_events = self.poll_incoming() had_any_events = self.poll_outgoing() or had_any_events num_fails = 0 while self.send_transaction_queue.qsize(): print('TRY_TO_SEND') tries, args, kw = self.send_transaction_queue.get() try: self._send_transaction(*args, **kw) except Exception as e: print('FAILED_TO_SEND', e, tries, args, kw) sleep(1) ## TODO self.send_transaction_queue.put((tries + 1, args, kw)) break return had_any_events def check_for_reorg( self, block_num, ): """ Check for reorg since last check, and reorgs during our reorg rewinding... """ print('START check_for_reorg', block_num) return block_num = ethereum.utils.parse_int_or_hex(block_num) while True: cur_num = block_num had_reorg = False while True: if cur_num == self.starting_block_num: break assert cur_num >= self.starting_block_num, ( cur_num, self.starting_block_num) ## Get info for prev and current: for x_block_num in [block_num, block_num - 1]: if x_block_num not in self.block_details: rh = self.c.eth_getBlockByNumber(x_block_num) ## Strip down to just a couple fields: block_h = { 'timestamp': ethereum.utils.parse_int_or_hex(rh['timestamp']), 'hash': rh['hash'], 'parentHash': rh['parentHash'], 'blockNumber': x_block_num, } self.block_details[x_block_num] = block_h ## Check for reorg: block_h = self.block_details[block_num] if block_h['parentHash'] != self.block_details[ block_h['blockNumber'] - 1]['hash']: print('!!! REORG', block_num, '->', cur_num) cur_num -= 1 self.latest_done_block = cur_num had_reorg = True continue break ## Rewind state if had_reorg: if had_reorg and (self.reorg_callback is not False): self.reorg_callback(cur_num) self.last_incoming_block = cur_num - 1 ## If had_reorg, loop again - to detect another reorg that occured while we tracked down the reorg... if not had_reorg: break return had_reorg def poll_incoming(self, chunk_size=50): """ https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter - track buffer of events from old blocks - track pointer to last processed block """ assert self.is_deployed, 'Must deploy contract first.' #self.latest_block_num = self.c.eth_blockNumber() from_block = self.last_incoming_block + 1 params = { 'from_block': fixed_int_to_hex(from_block), 'to_block': 'latest', #fixed_int_to_hex(from_block + chunk_size), 'address': self.contract_address, } print('eth_newFilter', 'from_block:', from_block, 'params:', params) self.the_filter = str(self.c.eth_newFilter(**params)) num_blocks = len(self.msgs) xx_msgs = self.c.eth_getFilterLogs(self.the_filter) for msg in xx_msgs: msg['blockNumber'] = ethereum.utils.parse_int_or_hex( msg['blockNumber']) if msg['blockNumber'] not in self.msgs: self.msgs[msg['blockNumber']] = [] self.msgs[msg['blockNumber']].append(msg) if num_blocks == len(self.msgs): ## Nothing new assert not len(xx_msgs), len(xx_msgs) return False for do_state, state_num_blocks in self.confirm_states.items(): longest_confirm_state = max(self.confirm_states.values()) newest_block_num = max(max(self.msgs), self.last_incoming_block) ## Oldest to newest: for nn in xrange( max(1, self.last_incoming_block - state_num_blocks), newest_block_num + 1, ): if self.check_for_reorg(nn): ## Just wait for next call to poll_incoming() before resuming. return False if nn in self.msgs: for msg in self.msgs[nn]: print('EMIT', do_state, nn, msg['data']) self.events_callback(msg=msg, receipt=False, received_via=do_state) ## Clear out old buffer: for nn in self.msgs.keys(): if nn < newest_block_num - longest_confirm_state - 1: del self.msgs[nn] self.last_incoming_block = newest_block_num return True if False: ## START CHECKS if do_state not in self.done_transactions: self.done_transactions[do_state] = set() self.done_block_nums[do_state] = set() msg_block_num = ethereum.utils.parse_int_or_hex(msg['blockNumber']) if cm == 0: assert msg_block_num not in self.done_block_nums[do_state], ( 'Seen block twice?', msg_block_num, ) self.done_block_nums[do_state].add(msg_block_num) if do_state in self.prev_block_num: assert msg_block_num >= self.prev_block_num[do_state], ( 'REORG?', msg_block_num, self.prev_block_num[do_state], ) self.prev_block_num[do_state] = msg_block_num assert msg['transactionHash'] not in self.done_transactions[ do_state], ( 'Seen transaction twice?', msg_block_num, msg['transactionHash'], ) self.done_transactions[do_state].add(msg['transactionHash']) ## END CHECKS return had_any_events def _start_contract_thread( self, terminate_on_exception=False, ): while True: try: had_any_events = self.loop_once() except Exception as e: print('-----LOOP_ONCE_EXCEPTION', e) #exit(-1) raise if terminate_on_exception: raise continue if not had_any_events: print('NO_NEW_EVENTS') sleep(self.contract_thread_sleep_time) def start_contract_thread( self, start_in_foreground=False, terminate_on_exception=False, ): """ Start ContractWrapper loop_once() in background thread, which (in that thread!) calls back to self.process_event() """ if start_in_foreground: self._start_contract_thread( terminate_on_exception=terminate_on_exception) else: self.t = Thread( target=self._start_contract_thread, args=(terminate_on_exception, ), ) self.t.daemon = True self.t.start() def send_transaction(self, *args, **kw): assert len(args) <= 2 if kw.get('block'): self.send_transaction(*args, **kw) else: self.send_transaction_queue.put((0, args, kw)) def _send_transaction( self, args_sig, args, callback=False, send_from=False, block=False, gas_limit=False, gas_price=100, value=100000000000, ): """ 1) Attempt to send transaction. 2) Get first confirmation via transaction receipt. 3) Re-check receipt again after N blocks pass. https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sendtransaction """ assert self.is_deployed, 'Must deploy contract first.' print('SEND_TRANSACTION:', args_sig, args) if send_from is False: send_from = self.c.eth_coinbase() send_to = self.contract_address print('====TRANSACTION') print('send_from', send_from) print('send_to', send_to) print('args_sig', args_sig) print('args', args) #print ('gas', gas_limit) gas_limit = 1000000 gas_price = self.c.DEFAULT_GAS_PRICE value = web3.utils.currency.to_wei(1, 'ether') data = self.c._encode_function(args_sig, args) data_hex = '0x' + data.encode('hex') tx = self.c.eth_sendTransaction( from_address=send_from, to_address=send_to, data=data_hex, gas=gas_limit, gas_price=gas_price, value=value, ) if block: print('BLOCKING FOR RECEIPT..') while True: receipt = self.c.eth_getTransactionReceipt( tx) ## blocks to ensure transaction is mined if receipt: break sleep(self.blocking_sleep_time) print('GOT RECEIPT') #print ('GOT_RECEIPT', receipt) #if receipt['blockNumber']: # self.latest_block_num = max(ethereum.utils.parse_int_or_hex(receipt['blockNumber']), self.latest_block_num) else: self.pending_transactions[tx] = (callback, self.latest_block_num) self.latest_block_num = self.c.eth_blockNumber() return tx def poll_outgoing(self): """ Confirm outgoing transactions. """ assert self.is_deployed, 'Must deploy contract first.' had_any_events = False if self.pending_transactions: had_any_events = True for tx, (callback, attempt_block_num) in self.pending_transactions.items(): ## Compare against the block_number where it attempted to be included: if (attempt_block_num <= self.latest_block_num - self.confirm_states['BLOCKCHAIN_CONFIRMED']): continue receipt = self.c.eth_getTransactionReceipt(tx) if receipt is not None and 'blockNumber' in receipt: actual_block_number = ethereum.utils.parse_int_or_hex( receipt['blockNumber']) else: ## TODO: wasn't confirmed after a long time. actual_block_number = False ## Now compare against the block_number where it was actually included: if (actual_block_number is not False) and ( actual_block_number >= self.latest_block_num - self.confirm_states['BLOCKCHAIN_CONFIRMED']): if callback is not False: callback(receipt) del self.pending_transactions[tx] return had_any_events def read_transaction(self, args_sig, value): rr = self.c.call(self.c.eth_coinbase(), self.contract_address, args_sig, value) return rr def sign(self, user_address, value): rr = self.c.eth_sign(self.c.eth_coinbase(), self.contract_address, user_address, value) return rr
def main(): # # check # assert os.path.isfile(CONTRACT) and 'contract file not found' # # now, just connect to our own blockchain node via rpc interface # try: rpc = EthJsonRpc(RPC_HOST, RPC_PORT) print('-' * 80) print('client software: {}'.format(rpc.web3_clientVersion())) print('block: {}'.format(rpc.eth_blockNumber())) print('address: {}'.format(rpc.eth_coinbase())) except: print('unable to connect to rpc server at {}:{}'.format( RPC_HOST, RPC_PORT)) sys.exit(-1) # # compile contract # print('-' * 80) print('compiling contract...') os.system('solcjs --bin {} > /dev/null'.format(CONTRACT)) with open(CONTRACT_COMPILED, 'r') as f: contract_bin = f.read() print(contract_bin) # # create contract # print('-' * 80) lastBlock = rpc.eth_blockNumber() contract_tx = rpc.create_contract( rpc.eth_coinbase(), contract_bin, gas=GAS) #send to address 0 by default to create contract print('contract sent, waiting for it to be mined...') # # get current block count # numTry = 0 contract_addr = None while True: curBlock = rpc.eth_blockNumber() if curBlock > lastBlock: lastBlock = curBlock numTry += 1 try: contract_addr = rpc.get_contract_address(contract_tx) if rpc.eth_getCode( contract_addr ) == '0x0': #it means the binary code of the contract is stil under compiling raise Exception() print('contract mined (block: {})'.format(curBlock)) break except: print('new block detected, but contract not mined') print('number of trying: {}'.format(numTry)) if numTry == WAIT_BLOCKS: print('publishing contract failed') sys.exit(-1) time.sleep(1) # # return contract addr # print('-' * 80) print('contract_address:\n\n\n--> {} <--\n\n'.format(contract_addr)) print('-' * 80)
def main(): # # receive contract addr # if len(sys.argv) != 3: print('Usage:\npython user.py <contract addr> <account addr>') sys.exit(-1) contract_addr = sys.argv[1] account_addr = sys.argv[2] # # create rpc interface # try: print('-' * 80) rpc = EthJsonRpc(RPC_HOST, RPC_PORT) print('client software: {}'.format(rpc.web3_clientVersion())) print('block: {}'.format(rpc.eth_blockNumber())) print('address: {}'.format(rpc.eth_coinbase())) except: print('unable to connect to rpc server at {}:{}'.format( RPC_HOST, RPC_PORT)) sys.exit(-1) # # check contract is online # print('-' * 80) if rpc.eth_getCode(contract_addr) == '0x0': print('!!! contract code not available on blockchain !!!') sys.exit(-1) print('found contract on blockchain!') # # console # topics = [] print('-' * 80) print('starting chat command line...') while True: # # simply read input # sys.stdout.write('>> ') command = sys.stdin.readline() # # quit? # if 'q' in command: sys.exit(0) # # show help # elif command == '\n' or 'help' in command: print('commands: help, send, status, topics, search, listen') # # compose new message # elif 'send' in command: print('-' * 80) print('[composing new message]') sys.stdout.write('message....: ') msg = sys.stdin.readline().strip() sys.stdout.write('image file.: ') img = sys.stdin.readline().strip() sys.stdout.write('custom tags: ') tag = sys.stdin.readline().strip() print('-' * 80) print('sending...') # loading image try: image = Image.open(img) except Exception as e: print('loading {} failed'.format(img)) continue # prediction print('precessing image...') label = ImageClassifier.predict(img) if label is None: print('classification failed') continue print('label: {}'.format(label)) tag += ' #' + label bs = ImageHelper.imgToBytes(image) tx = rpc.call_with_transaction( account_addr, contract_addr, 'setNewUserState(string,bytes,string)', [msg, bs, tag], gas=GAS) print('done, transaction id: {}'.format(tx)) # # get own last post # elif 'status' in command: print('-' * 80) print('[receiving last post]') userMessage, userImage, userTags = rpc.call( contract_addr, 'getUserState(address)', [account_addr], ['string', 'bytes', 'string']) if not userMessage: print('nothing posted yet') continue print(' content: {}'.format(userMessage)) print(' tags...: {}'.format(userTags)) ImageHelper.bytesToImg(userImage).show() # # set tag filters # elif 'topics' in command: topics = [t.strip() for t in command.split()[1:]] if len(topics) == 0: print('please provide actual topics after <topics> command') continue print('filter set for messages on topics: {}'.format(topics)) # # search complete blockchain for messages with certain tags # elif 'search' in command: if len(topics) == 0: print('call topics first') continue curBlock = rpc.eth_blockNumber() for i in range(curBlock + 1): for trans in rpc.eth_getBlockByNumber(i)['transactions']: res = Decoder.decodeABI(trans['input']) if res is None: continue msg, code, tags = res if all(t not in tags for t in topics): continue print('-' * 80) print('message from user {} (block {}):'.format( trans['from'], i)) print(' content: {}'.format(msg)) print(' tags...: {}'.format(tags)) ImageHelper.bytesToImg(code).show(title='{}'.format(tags)) # # start listening for messages # elif 'listen' in command: if len(topics) == 0: print('call topics first') continue global LISTENING LISTENING = True curBlock = rpc.eth_blockNumber() while LISTENING: newBlock = rpc.eth_blockNumber() if newBlock > curBlock: print('new block detected ({})'.format(newBlock)) curBlock = newBlock for trans in rpc.eth_getBlockByNumber( newBlock)['transactions']: res = Decoder.decodeABI(trans['input']) if res is None: continue msg, code, tags = res if all(t not in tags for t in topics): continue print('-' * 80) print('message from user {} (block {}):'.format( trans['from'], newBlock)) print(' content: {}'.format(msg)) print(' tags...: {}'.format(tags)) ImageHelper.bytesToImg(code).show( title='{}'.format(tags)) time.sleep(1) # # default response # else: print('command not recognized')
class EthereumBlockchain: def __init__(self, the_code = False, the_sig = None, the_args = None, the_address = False, rpc_host = DEFAULT_RPC_HOST, rpc_port = DEFAULT_RPC_PORT, background_thread_sleep_time = 1.0, ): self.con = EthJsonRpc(rpc_host, rpc_port) self.the_code = the_code self.the_sig = the_sig self.the_args = the_args self.contract_address = the_address assert self.the_code or self.contract_address self.loop_once_started = False self.sig_to_topic_id_cache = {} self.send_transaction_queue = Queue() self.background_thread_sleep_time = background_thread_sleep_time self.pending_transactions = {} ## {tx:callback} self.is_deployed = False if the_address: assert self.check_anything_deployed(the_address), ('NOTHING DEPLOYED AT SPECIFIED ADDRESS:', the_address) self.is_deployed = True elif the_code: if auto_deploy: self.deploy() #def setup_create_pending_transaction(func): # self._create_pending_transaction = func #def setup_logic_callback(func): # self._logic_callback = func def event_sig_to_topic_id(self, sig): """ Compute ethereum topic_ids from function signatures. """ if sig in self.sig_to_topic_id_cache: return self.sig_to_topic_id_cache[sig] name = sig[:sig.find('(')] types = [x.strip().split()[0] for x in sig[sig.find('(')+1:sig.find(')')].split(',')] topic_id = ethereum.utils.int_to_hex(ethereum.abi.event_id(name,types)) self.sig_to_topic_id_cache[sig] = topic_id return topic_id def setup_event_callbacks(self, log_handlers, pending_handlers, ): self.log_handlers = log_handlers self.log_handlers_hashed = {((x == 'DEFAULT') and 'DEFAULT' or self.event_sig_to_topic_id(x)):y for x,y in log_handlers.items()} self.pending_handlers = pending_handlers def logic_callback(self, msg, *args, **kw): default_func = self.log_handlers_hashed.get('DEFAULT', False) if 'topics' not in msg: assert default_func is not False, ('Unknown topic_id and no DEFAULT handler.') print ('PROXY logic_callback()', '->', default_func, kw) default_func(msg, *args, **kw) return for topic in msg['topics']: func = self.log_handlers_hashed.get(topic, default_func) print ('PROXY logic_callback()', topic, '->', func) assert func is not False, ('Unknown topic_id and no DEFAULT handler.', topic, kw) func(msg, *args, **kw) def simulate_pending(self, args_sig, args, *aa, **bb): """ See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#events """ ## TODO: support indexed topics? func = self.pending_handlers.get(args_sig, self.pending_handlers.get('DEFAULT', False)) print ('Proxy: simulate_pending()', args_sig, '->', func) return func(args_sig, args, *aa, **bb) def get_block_by_hash_callback(self, block_hash): """ Get block by blockHash. https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbyhash """ print ('bcc.get_block_by_hash_callback()', block_hash) rh = self.con.eth_getBlockByHash(block_hash) for k in ['number', 'timestamp']: rh[k] = ethereum.utils.parse_int_or_hex(rh[k]) return rh def get_logs_by_block_num_callback(self, block_num): """ Get event logs for a particular block num. It's OK if the block_num has an unexpected hash, that'll be taken care of by caller. https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs """ print ('bcc.get_logs_by_block_num_callback()', block_num) the_filter = self.con.eth_newFilter(from_block = fixed_int_to_hex(block_num), to_block = fixed_int_to_hex(block_num), address = self.contract_address, ) rr = self.con.eth_getFilterLogs(str(the_filter)) rr2 = [] for msg in rr: #assert 'topics' in msg, list(sorted(msg.keys())) got_num = ethereum.utils.parse_int_or_hex(msg['blockNumber']) if got_num != block_num: continue msg['data'] = solidity_string_decode(msg['data']) msg['blockNumber'] = got_num msg["logIndex"] = ethereum.utils.parse_int_or_hex(msg['logIndex']) msg["transactionIndex"] = ethereum.utils.parse_int_or_hex(msg['transactionIndex']) #msg_data = loads_compact(msg['data']) #payload_decoded = loads_compact(msg_data['payload']) rr2.append(msg) rr = rr2 return rr def get_latest_block_number(self): print ('bcc.get_latest_block_number()') bn = self.con.eth_blockNumber() return bn def get_latest_block_callback(self): """ Returns the single latest block. Missing intermediate blocks will be automatically looked up by caller. """ print ('bcc.get_latest_block_callback()') bn = self.get_latest_block_number() block = self.con.eth_getBlockByNumber(bn) return block def check_anything_deployed(self, address): """ Basic sanity check, checks if ANY code is deployed at provided address. """ print ('bcc.check_anything_deployed()', address) if self.con.eth_getCode(address) == '0x0': return False return True def deploy(self, the_sig = False, the_args = False, block = False, deploy_from = False, callback = False, ): """ Deploy contract. Optional args_sig and args used to pass arguments to contract constructor.""" print ('bcc.deploy()') if the_sig is not False: self.contract_sig = the_sig if the_args is not False: self.contract_args = the_args assert self.the_code if deploy_from is False: deploy_from = self.con.eth_coinbase() print ('DEPLOYING_CONTRACT...', 'deploy_from:', deploy_from, 'the_sig:', the_sig, 'the_args:', the_args) # get contract address xx = self.con.eth_compileSolidity(self.the_code) #print ('GOT',xx) compiled = None try: compiled = xx['code'] except KeyError: # geth seems to like putting the compiler output into an inner dict keyed by input filename, # e.g {'CCCoinToken.sol': {'code': '...', 'etc': '...'} for k, v in xx.iteritems(): if isinstance(v, dict) and 'code' in v: compiled = v['code'] break assert compiled contract_tx = self.con.create_contract(from_ = deploy_from, code = compiled, gas = 3000000, sig = self.contract_sig, args = self.contract_args, ) if block: ## NOTE: @yusef feel free to switch back to this method if you want: #print('CONTRACT DEPLOYED, WAITING FOR CONFIRMATION') #wait_for_confirmation(self.c, contract_tx) print ('BLOCKING FOR RECEIPT..') while True: receipt = self.con.eth_getTransactionReceipt(contract_tx) ## blocks to ensure transaction is mined if receipt: break sleep(self.blocking_sleep_time) print ('GOT RECEIPT') else: self.pending_transactions[contract_tx] = callback self.contract_address = str(self.con.get_contract_address(contract_tx)) self.is_deployed = True print ('DEPLOYED', self.contract_address) return self.contract_address def send_transaction(self, *args, **kw): """ 1) If possible create simulated outputs of transactions, to use as pending state. 2) Queue the transaction for blockchain commit. Used for e.g.: - addLog(bytes) - mintTokens(address, uint, uint, uint, uint, uint, uint) - withdrawTok(bytes) - lockupTok(bytes) """ print ('bcc.send_transaction()') assert len(args) <= 2 #assert self.loop_once_started, 'loop_once() not started?' ## Potentially slow blocking call to commit it to the blockchain: if kw.get('block'): ## Send transaction in blocking mode, and grab actual event logs that are committed: rh = self.inner_send_transaction(*args, **kw) pending_logs = rh['pending_logs'] is_pending = False else: ## Run callbacks, return simulated event logs where possible: self.send_transaction_queue.put((0, args, kw)) pending_logs = self.simulate_pending(*args, **kw) is_pending = True ## Run logic_callback() against pending transactions: for log in pending_logs: print ('CALLING', 'is_pending:', is_pending, 'is_noop:', log.get('is_noop', False)) self.logic_callback(log, is_pending = is_pending, is_noop = log.get('is_noop', False) ) return pending_logs def inner_send_transaction(self, args_sig, args, callback = False, send_from = False, block = False, gas_limit = False, gas_price = 100, value = 100000000000, ): """ 1) Attempt to send transaction. 2) Get first confirmation via transaction receipt. 3) Re-check receipt again after N blocks pass. https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sendtransaction """ print ('bcc.inner_send_transaction()') assert self.is_deployed, 'Must deploy contract first.' print ('SEND_TRANSACTION:', args_sig, args) if send_from is False: send_from = self.con.eth_coinbase() send_to = self.contract_address print ('====TRANSACTION') print ('send_from', send_from) print ('send_to', send_to) print ('args_sig', args_sig) print ('args', args) #print ('gas', gas_limit) gas_limit = 1000000 gas_price = self.con.DEFAULT_GAS_PRICE value = web3.utils.currency.to_wei(1,'ether') data = self.con._encode_function(args_sig, args) data_hex = '0x' + data.encode('hex') tx = self.con.eth_sendTransaction(from_address = send_from, to_address = send_to, data = data_hex, gas = gas_limit, gas_price = gas_price, value = value, ) if block: print ('BLOCKING FOR RECEIPT..') while True: receipt = self.con.eth_getTransactionReceipt(tx) ## blocks to ensure transaction is mined if receipt: break sleep(self.blocking_sleep_time) print ('GOT RECEIPT') #print ('GOT_RECEIPT', receipt) #if receipt['blockNumber']: # self.latest_block_num = max(ethereum.utils.parse_int_or_hex(receipt['blockNumber']), self.latest_block_num) else: self.pending_transactions[tx] = callback #self.latest_block_num = self.con.eth_blockNumber() return {'tx':tx} def poll_outgoing_receipts(self): """ Check for transaction receipts on transactions sent from this node. TODO - why not replace this with just watching for incoming transactions??? """ assert self.is_deployed, 'Must deploy contract first.' had_any_events = False if self.pending_transactions: had_any_events = True for tx, callback in self.pending_transactions.items(): receipt = self.con.eth_getTransactionReceipt(tx) if receipt is not None and 'blockNumber' in receipt: actual_block_number = ethereum.utils.parse_int_or_hex(receipt['blockNumber']) else: ## TODO: wasn't confirmed after a long time. actual_block_number = False ## Now compare against the block_number where it was actually included: if actual_block_number is not False: if callback is not False: callback(receipt) del self.pending_transactions[tx] return had_any_events def _start_background_thread(self, terminate_on_exception = False, ): last_event = False while True: try: had_any_events = self.loop_once_blockchain() except Exception as e: print ('-----LOOP_ONCE_EXCEPTION', e) #exit(-1) raise if terminate_on_exception: raise continue if had_any_events: last_event = time() else: print ('NO_NEW_EVENTS', last_event and (time() - last_event)) sleep(self.background_thread_sleep_time) def start_background_thread(self, start_in_foreground = False, terminate_on_exception = False, ): """ Start ContractWrapper loop_once() in background thread, which (in that thread!) calls back to self.process_event() """ if start_in_foreground: self._start_background_thread(terminate_on_exception = terminate_on_exception) else: self.t = Thread(target = self._start_background_thread, args = (terminate_on_exception,), ) self.t.daemon = True self.t.start() def loop_once_blockchain(self): ## Check for available write receipts: #self.poll_outgoing_receipts() self.loop_once_started = True ## Do write transactions transactions: while self.send_transaction_queue.qsize(): print ('TRY_TO_SEND') tries, args, kw = self.send_transaction_queue.get() try: self.inner_send_transaction(*args, **kw) except Exception as e: print ('FAILED_TO_SEND', e, tries, args, kw) sleep(1) ## TODO self.send_transaction_queue.put((tries + 1, args, kw)) break
class Deploy: def __init__(self, protocol, host, port, add_dev_code, verify_code, contract_dir, gas, gas_price, private_key): self.pp = PreProcessor() self.s = t.state() self.s.block.number = 1150000 # Homestead t.gas_limit = int(gas) self.json_rpc = EthJsonRpc(protocol=protocol, host=host, port=port) if private_key: self.user_address = '0x' + privtoaddr( private_key.decode('hex')).encode('hex') else: self.user_address = self.json_rpc.eth_coinbase()["result"] self.add_dev_code = add_dev_code == 'true' self.verify_code = verify_code == 'true' self.contract_dir = contract_dir self.gas = int(gas) self.gas_price = int(gas_price) self.private_key = private_key self.contract_addresses = {} self.contract_abis = {} def wait_for_transaction_receipt(self, transaction_hash): while self.json_rpc.eth_getTransactionReceipt( transaction_hash)['result'] is None: logging.info( 'Waiting for transaction receipt {}'.format(transaction_hash)) time.sleep(5) def replace_address(self, a): return self.contract_addresses[a] if isinstance( a, basestring) and a in self.contract_addresses else a def get_nonce(self): return int( self.json_rpc.eth_getTransactionCount( self.user_address)["result"][2:], 16) def get_raw_transaction(self, data, contract_address=''): nonce = self.get_nonce() tx = Transaction(nonce, self.gas_price, self.gas, contract_address, 0, data.decode('hex')) tx.sign(self.private_key.decode('hex')) return rlp.encode(tx).encode('hex') def code_is_valid(self, contract_address, compiled_code): deployed_code = self.json_rpc.eth_getCode(contract_address)["result"] locally_deployed_code_address = self.s.evm( compiled_code.decode("hex")).encode("hex") locally_deployed_code = self.s.block.get_code( locally_deployed_code_address).encode("hex") return deployed_code == "0x" + locally_deployed_code @staticmethod def compile_code(code, language): combined = languages[language].combined(code) compiled_code = combined[-1][1]["bin_hex"] abi = combined[-1][1]["abi"] return compiled_code, abi @staticmethod def replace_library_placeholders(bytecode, addresses): if addresses: for library_name, library_address in addresses.iteritems(): bytecode = bytecode.replace( "__{}{}".format(library_name, "_" * (38 - len(library_name))), library_address[2:]) return bytecode def deploy_code(self, file_path, params, addresses): if addresses: addresses = dict([(k, self.replace_address(v)) for k, v in addresses.iteritems()]) language = "solidity" if file_path.endswith(".sol") else "serpent" code = self.pp.process(file_path, add_dev_code=self.add_dev_code, contract_dir=self.contract_dir, addresses=addresses) # compile code bytecode, abi = self.compile_code(code, language) # replace library placeholders bytecode = self.replace_library_placeholders(bytecode, addresses) if params: translator = ContractTranslator(abi) # replace constructor placeholders params = [self.replace_address(p) for p in params] bytecode += translator.encode_constructor_arguments(params).encode( "hex") logging.info( 'Try to create contract with length {} based on code in file: {}'. format(len(bytecode), file_path)) if self.private_key: raw_tx = self.get_raw_transaction(bytecode) tx_response = self.json_rpc.eth_sendRawTransaction("0x" + raw_tx) while "error" in tx_response: logging.info('Deploy failed with error {}. Retry!'.format( tx_response['error'])) time.sleep(5) tx_response = self.json_rpc.eth_sendRawTransaction("0x" + raw_tx) else: tx_response = self.json_rpc.eth_sendTransaction( self.user_address, data=bytecode, gas=self.gas, gas_price=self.gas_price) while "error" in tx_response: logging.info('Deploy failed with error {}. Retry!'.format( tx_response['error'])) time.sleep(5) tx_response = self.json_rpc.eth_sendTransaction( self.user_address, data=bytecode, gas=self.gas, gas_price=self.gas_price) transaction_hash = tx_response['result'] self.wait_for_transaction_receipt(transaction_hash) contract_address = self.json_rpc.eth_getTransactionReceipt( transaction_hash)["result"]["contractAddress"] # Verify deployed code with locally deployed code if self.verify_code and not self.code_is_valid(contract_address, bytecode): logging.info('Deploy of {} failed. Retry!'.format(file_path)) self.deploy_code(file_path, params, addresses) contract_name = file_path.split("/")[-1].split(".")[0] self.contract_addresses[contract_name] = contract_address self.contract_abis[contract_name] = abi logging.info('Contract {} was created at address {}.'.format( file_path, contract_address)) def send_transaction(self, contract, name, params): contract_address = self.replace_address(contract) contract_abi = self.contract_abis[contract] translator = ContractTranslator(contract_abi) data = translator.encode(name, [self.replace_address(p) for p in params]).encode("hex") logging.info('Try to send {} transaction to contract {}.'.format( name, contract)) if self.private_key: raw_tx = self.get_raw_transaction(data, contract_address) tx_response = self.json_rpc.eth_sendRawTransaction("0x" + raw_tx) while 'error' in tx_response: logging.info('Transaction failed with error {}. Retry!'.format( tx_response['error'])) time.sleep(5) tx_response = self.json_rpc.eth_sendRawTransaction("0x" + raw_tx) else: tx_response = self.json_rpc.eth_sendTransaction( self.user_address, to_address=contract_address, data=data, gas=self.gas, gas_price=self.gas_price) while 'error' in tx_response: logging.info('Transaction failed with error {}. Retry!'.format( tx_response['error'])) time.sleep(5) tx_response = self.json_rpc.eth_sendTransaction( self.user_address, to_address=contract_address, data=data, gas=self.gas, gas_price=self.gas_price) transaction_hash = tx_response['result'] self.wait_for_transaction_receipt(transaction_hash) logging.info('Transaction {} for contract {} completed.'.format( name, contract)) def assert_call(self, contract, name, params, return_value): contract_address = self.replace_address(contract) return_value = self.replace_address(return_value) contract_abi = self.contract_abis[contract] translator = ContractTranslator(contract_abi) data = translator.encode(name, [self.replace_address(p) for p in params]).encode("hex") logging.info('Try to assert return value of {} in contract {}.'.format( name, contract)) bc_return_val = self.json_rpc.eth_call(to_address=contract_address, data=data)["result"] result_decoded = translator.decode(name, bc_return_val[2:].decode("hex")) result_decoded = result_decoded if len( result_decoded) > 1 else result_decoded[0] assert result_decoded == return_value logging.info( 'Assertion successful for return value of {} in contract {}.'. format(name, contract)) def process(self, f): with open(f) as data_file: instructions = json.load(data_file) logging.info('Your address: {}'.format(self.user_address)) for instruction in instructions: logging.info('Your balance: {} Wei'.format( int( self.json_rpc.eth_getBalance( self.user_address)['result'], 16))) if instruction["type"] == "deployment": self.deploy_code( instruction["file"], instruction["params"] if "params" in instruction else None, instruction["addresses"] if "addresses" in instruction else None, ) elif instruction["type"] == "transaction": self.send_transaction( instruction["contract"], instruction["name"], instruction["params"] if "params" in instruction else [], ) elif instruction["type"] == "assertion": self.assert_call( instruction["contract"], instruction["name"], instruction["params"] if "params" in instruction else [], instruction["return"]) for contract_name, contract_address in self.contract_addresses.iteritems( ): logging.info('Contract {} was created at address {}.'.format( contract_name, contract_address))
def main(): CONTRACT = sys.argv[1] # # just load contract code from file (e.g. contract.sol) # assert os.path.isfile(CONTRACT) and 'contract file not found ({})'.format( CONTRACT) with open(CONTRACT, 'r') as f: CONTRACT_CODE = f.read() # # now, just connect to our own blockchain node via rpc interface # try: rpc = EthJsonRpc(RPC_HOST, RPC_PORT) print('-' * 80) print('client software: {}'.format(rpc.web3_clientVersion())) print('block: {}'.format(rpc.eth_blockNumber())) print('address: {}'.format(rpc.eth_coinbase())) except: print('unable to connect to rpc server at {}:{}'.format( RPC_HOST, RPC_PORT)) sys.exit(-1) # # compile contract # print('-' * 80) print('compiling contract...') compiled = rpc.eth_compileSolidity(CONTRACT_CODE) contract_bin = compiled['code'] # # create contract # print('-' * 80) lastBlock = rpc.eth_blockNumber() contract_tx = rpc.create_contract(rpc.eth_coinbase(), contract_bin, gas=GAS) print('contract sent, waiting for it to be mined...') # # get current block count # numTry = 0 contract_addr = None while True: curBlock = rpc.eth_blockNumber() if curBlock > lastBlock: lastBlock = curBlock numTry += 1 try: contract_addr = rpc.get_contract_address(contract_tx) if rpc.eth_getCode(contract_addr) == '0x0': raise Exception() print('contract mined (block: {})'.format(curBlock)) break except: print('new block detected, but contract not mined') if numTry == WAIT_BLOCKS: print('publishing contract failed') sys.exit(-1) time.sleep(1) # # return contract addr # print('-' * 80) print('contract_address:\n\n\n--> {} <--\n\n'.format(contract_addr)) print('-' * 80)