async def sendRawBlock(self, encoded_micro_block): chain = self.get_new_chain() encoded_micro_block = decode_hex(encoded_micro_block) micro_block = rlp.decode(encoded_micro_block, sedes=chain.get_vm().micro_block_class) block_class = self._chain_class.get_vm_class_for_block_timestamp(timestamp = micro_block.header.timestamp).get_block_class() full_block = block_class.from_micro_block(micro_block) min_time_between_blocks = chain.get_vm(header=full_block.header).min_time_between_blocks # Validate the block here if(full_block.header.timestamp < (int(time.time()) - MAX_ALLOWED_AGE_OF_NEW_RPC_BLOCK)): raise BaseRPCError("The block timestamp is to old. We can only import new blocks over RPC.") try: canonical_head = chain.chaindb.get_canonical_head(full_block.header.chain_address) if canonical_head.block_number >= full_block.header.block_number: raise BaseRPCError("You are attempting to replace an existing block. This is not allowed.") if full_block.header.timestamp < (canonical_head.timestamp + min_time_between_blocks): raise BaseRPCError("Not enough time has passed for you to add a new block yet. New blocks can only be added to your chain every {} seconds".format(min_time_between_blocks)) except CanonicalHeadNotFound: pass if((full_block.header.block_number != 0) and (not chain.chaindb.is_in_canonical_chain(full_block.header.parent_hash))): raise BaseRPCError("Parent block not found on canonical chain.") #Check our current syncing stage. Must be sync stage 4. current_sync_stage_response = await self._event_bus.request( CurrentSyncStageRequest() ) if current_sync_stage_response.sync_stage < FULLY_SYNCED_STAGE_ID: raise BaseRPCError("This node is still syncing with the network. Please wait until this node has synced.") if not does_block_meet_min_gas_price(full_block, chain): required_min_gas_price = self._chain.chaindb.get_required_block_min_gas_price() raise Exception("Block transactions don't meet the minimum gas price requirement of {}".format(required_min_gas_price)) self._event_bus.broadcast( NewBlockEvent(block=cast(P2PBlock, full_block), from_rpc=True) ) return True
async def filterAddressesWithReceivableTransactions( self, chain_addresses, after_timestamp=0): # # Checks all of the given chain_addresses for receivable transactions, and returns a list of chain addresses that have any. # if len(chain_addresses) < 1: raise BaseRPCError( "Must provide at least one chain address when calling getAddressesWithReceivableTransactions" ) earliest_chronological_timestamp = int( int(time.time()) - TIME_BETWEEN_HEAD_HASH_SAVE * NUMBER_OF_HEAD_HASH_TO_SAVE * 0.95) # create new chain for all requests chain = self.get_new_chain() chain_addresses = [Address(decode_hex(x)) for x in chain_addresses] if isinstance( after_timestamp, int) and after_timestamp > earliest_chronological_timestamp: # cycle through all chronological windows _, addresses_with_receivable_transactions = await chain.coro_get_receivable_transaction_hashes_from_chronological( after_timestamp, chain_addresses) else: addresses_with_receivable_transactions = await chain.coro_filter_accounts_with_receivable_transactions( chain_addresses) addresses_with_receivable_transactions = [ to_checksum_address(x) for x in addresses_with_receivable_transactions ] return addresses_with_receivable_transactions
async def getBlockTransactionCountByHash(self, block_hash): chain = self.get_new_chain() try: tx_count = chain.chaindb.get_number_of_total_tx_in_block(block_hash) except HeaderNotFound: raise BaseRPCError('No block found with the given block hash') return hex(tx_count)
async def getFaucet(self, chain_address): current_sync_stage_response = await self._event_bus.request( CurrentSyncStageRequest()) if current_sync_stage_response.sync_stage < FULLY_SYNCED_STAGE_ID: raise BaseRPCError( "This node is still syncing with the network. Please wait until this node has synced." ) chain_object = self.get_new_chain( self._chain_class.faucet_private_key.public_key. to_canonical_address(), private_key=self._chain_class.faucet_private_key) receivable_transactions, _ = chain_object.get_receivable_transactions( chain_address) total_receivable = 0 for tx in receivable_transactions: total_receivable += tx.value if (chain_object.get_vm().state.account_db.get_balance(chain_address) + total_receivable) < 5 * 10**18: gas_price = int( to_wei( int(chain_object.chaindb.get_required_block_min_gas_price( ) + 5), 'gwei')) chain_object.create_and_sign_transaction_for_queue_block( gas_price=gas_price, gas=0x0c3500, to=chain_address, value=int(1 * 10**18), data=b"", v=0, r=0, s=0) chain_object.import_current_queue_block()
async def getBlockTransactionCountByNumber(self, at_block, chain_address): chain = self.get_new_chain() try: block_hash = chain.chaindb.get_canonical_block_hash(chain_address=chain_address, block_number=at_block) tx_count = chain.chaindb.get_number_of_total_tx_in_block(block_hash) except HeaderNotFound: raise BaseRPCError('No block found with the given wallet address and block number') return hex(tx_count)
async def getTransactionByHash(self, tx_hash): chain = self.get_new_chain() try: tx = chain.get_canonical_transaction(tx_hash) except TransactionNotFound: raise BaseRPCError("Transaction with hash {} not found on canonical chain.".format(encode_hex(tx_hash))) if isinstance(tx, BaseReceiveTransaction): return receive_transaction_to_dict(tx, chain) else: return transaction_to_dict(tx, chain)
async def _get_unlocked_account_or_unlock_now(self, wallet_address: bytes, password: str = None): normalized_wallet_address = to_normalized_address(wallet_address) if password is None or password == '': try: account = self._unlocked_accounts[normalized_wallet_address] except KeyError: raise BaseRPCError("No unlocked account found with wallet address {}".format(normalized_wallet_address)) else: account = await self._unlock_account(wallet_address, password) return account
async def getReceiveTransactionOfSendTransaction(self, tx_hash): ''' Gets the receive transaction corresponding to a given send transaction, if it exists ''' chain = self.get_new_chain() receive_tx = chain.get_receive_tx_from_send_tx(tx_hash) if receive_tx is not None: receive_tx_dict = receive_transaction_to_dict(receive_tx, chain) return receive_tx_dict else: raise BaseRPCError("No receive transaction found for the given send transaction hash")
async def getTransactionByBlockHashAndIndex(self, block_hash, index): try: tx = self._chain.get_transaction_by_block_hash_and_index(block_hash, index) except HeaderNotFound: raise BaseRPCError('No block found with the given block hash') if isinstance(tx, BaseReceiveTransaction): # receive tx return receive_transaction_to_dict(tx, self._chain) else: # send tx return transaction_to_dict(tx, self._chain)
async def receiveTransactions(self, wallet_address: bytes, password: str = None): # Check our current syncing stage. Must be sync stage 4. current_sync_stage_response = await self._event_bus.request( CurrentSyncStageRequest() ) if current_sync_stage_response.sync_stage < FULLY_SYNCED_STAGE_ID: raise BaseRPCError("This node is still syncing with the network. Please wait until this node has synced.") wallet_address_hex = encode_hex(wallet_address) account = await self._get_unlocked_account_or_unlock_now(wallet_address_hex, password) return await self._send_transactions([], account)
async def getTransactionByBlockNumberAndIndex(self, at_block, index, chain_address): try: block_hash = self._chain.chaindb.get_canonical_block_hash(chain_address=chain_address, block_number=at_block) except HeaderNotFound: raise BaseRPCError('No block found with the given chain address and block number') tx = self._chain.get_transaction_by_block_hash_and_index(block_hash, index) if isinstance(tx, BaseReceiveTransaction): # receive tx return receive_transaction_to_dict(tx, self._chain) else: # send tx return transaction_to_dict(tx, self._chain)
def _save_account(self, account, password): if not self._account_address_cache_ready.is_set(): raise BaseRPCError("Account cache is still building. Please wait and try again in a moment.") w3 = Web3() new_account_json_encrypted = w3.hls.account.encrypt(account.privateKey, password) keyfile_name = "HLS_account_{}".format(account.address) keyfile_path = self._rpc_context.keystore_dir / keyfile_name f = open(str(keyfile_path), "w") f.write(json.dumps(new_account_json_encrypted)) f.close() self._account_address_cache.add(account.address)
def _get_keystore_for_address(self, wallet_address): normalized_wallet_address = to_normalized_address(wallet_address) file_glob = self.rpc_context.keystore_dir.glob('**/*') files = [x for x in file_glob if x.is_file()] for json_keystore in files: try: with open(str(json_keystore)) as json_file: keystore = json.load(json_file) if 'address' in keystore: if normalized_wallet_address == to_normalized_address(keystore['address']): return keystore except Exception as e: # Not a json file pass raise BaseRPCError("No saved keystore for wallet address {}".format(normalized_wallet_address))
async def sendTransactions(self, txs, password: str = None): ''' :param tx: {'from', 'to', 'value', 'gas', 'gasPrice', 'data', 'nonce'} :param password: :return: ''' # Check our current syncing stage. Must be sync stage 4. current_sync_stage_response = await self._event_bus.request( CurrentSyncStageRequest() ) if current_sync_stage_response.sync_stage < FULLY_SYNCED_STAGE_ID: raise BaseRPCError("This node is still syncing with the network. Please wait until this node has synced.") wallet_address_hex = txs[0]['from'] account = await self._get_unlocked_account_or_unlock_now(wallet_address_hex, password) return await self._send_transactions(txs, account)
async def _send_transactions(self, transactions, account, include_receive: bool = True): async with self._importing_block_lock: print("Importing block") normalized_wallet_address = to_normalized_address(account.address) wallet_address_hex = account.address wallet_address = decode_hex(wallet_address_hex) chain = self.get_new_chain(Address(wallet_address), account._key_obj) allowed_time_of_next_block = chain.get_allowed_time_of_next_block() now = int(time.time()) if now < allowed_time_of_next_block: raise BaseRPCError("The minimum time between blocks has not passed. You must wait until {} to send the next block. " "Use personal_sendTrasactions to send multiple transactions at once.".format(allowed_time_of_next_block)) # make the chain read only for creating the block. We don't want to actually import it here. chain.enable_read_only_db() if include_receive: chain.populate_queue_block_with_receive_tx() signed_transactions = [] min_gas_price = to_wei(chain.chaindb.get_required_block_min_gas_price(), 'gwei') safe_min_gas_price = to_wei(chain.chaindb.get_required_block_min_gas_price()+5, 'gwei') for i in range(len(transactions)): tx = transactions[i] if to_normalized_address(tx['from']) != normalized_wallet_address: raise BaseRPCError("When sending multiple transactions at once, they must all be from the same address") if 'gasPrice' in tx: gas_price = to_int_if_hex(tx['gasPrice']) else: gas_price = safe_min_gas_price if 'gas' in tx: gas = to_int_if_hex(tx['gas']) else: gas = GAS_TX if 'data' in tx: data = tx['data'] else: data = b'' if 'nonce' in tx: nonce = to_int_if_hex(tx['nonce']) else: nonce = None transactions[i]['nonce'] = nonce signed_tx = chain.create_and_sign_transaction_for_queue_block( gas_price=gas_price, gas=gas, to=decode_hex(tx['to']), value=to_int_if_hex(tx['value']), data=data, nonce=nonce, v=0, r=0, s=0 ) signed_transactions.append(signed_tx) block = chain.import_current_queue_block() if not does_block_meet_min_gas_price(block, chain): raise Exception("The average gas price of all transactions in your block does not meet the required minimum gas price. Your average block gas price: {}. Min gas price: {}".format( get_block_average_transaction_gas_price(block), min_gas_price)) if len(signed_transactions) == 0 and len(block.receive_transactions) == 0: raise BaseRPCError("Cannot send block if it has no send or receive transactions.") self._event_bus.broadcast( NewBlockEvent(block=cast(P2PBlock, block), from_rpc=True) ) send_transaction_hashes = [encode_hex(tx.hash) for tx in signed_transactions] receive_transaction_hashes = [encode_hex(tx.hash) for tx in block.receive_transactions] all_transaction_hashes = send_transaction_hashes all_transaction_hashes.extend(receive_transaction_hashes) if not include_receive: return all_transaction_hashes[0] else: return all_transaction_hashes
async def _send_transactions(self, transactions, account, include_receive: bool = True): async with self._importing_block_lock: print("Importing block") # Check our current syncing stage. Must be sync stage 4. current_sync_stage_response = await self._event_bus.request( CurrentSyncStageRequest()) if current_sync_stage_response.sync_stage < FULLY_SYNCED_STAGE_ID: raise BaseRPCError( "This node is still syncing with the network. Please wait until this node has synced." ) # Check that our block import queue is not deep. It could cause min gas to increase before this block makes it there. current_block_import_queue_length = await self._event_bus.request( BlockImportQueueLengthRequest()) if current_block_import_queue_length.queue_length > MAX_ALLOWED_LENGTH_BLOCK_IMPORT_QUEUE: raise BaseRPCError( "This node is currently at it's maximum capacity of new blocks. Please wait a moment while we process them, and try again." ) normalized_wallet_address = to_normalized_address(account.address) wallet_address_hex = account.address wallet_address = decode_hex(wallet_address_hex) chain = self.get_new_chain(Address(wallet_address), account._key_obj) allowed_time_of_next_block = chain.get_allowed_time_of_next_block() now = int(time.time()) if now < allowed_time_of_next_block: raise BaseRPCError( "The minimum time between blocks has not passed. You must wait until {} to send the next block. " "Use personal_sendTrasactions to send multiple transactions at once." .format(allowed_time_of_next_block)) # make the chain read only for creating the block. We don't want to actually import it here. chain.enable_read_only_db() if include_receive: chain.populate_queue_block_with_receive_tx() signed_transactions = [] # Make sure it meets the average min gas price of the network. If it doesnt, then it won't reach consensus. current_network_min_gas_price_response = await self._event_bus.request( AverageNetworkMinGasPriceRequest()) network_min_gas_price = current_network_min_gas_price_response.min_gas_price local_min_gas_price = chain.min_gas_db.get_required_block_min_gas_price( ) actual_min_gas_price = max( [network_min_gas_price, local_min_gas_price]) min_gas_price = to_wei(actual_min_gas_price, 'gwei') safe_min_gas_price = to_wei(actual_min_gas_price + 5, 'gwei') for i in range(len(transactions)): tx = transactions[i] if to_normalized_address( tx['from']) != normalized_wallet_address: raise BaseRPCError( "When sending multiple transactions at once, they must all be from the same address" ) if 'gasPrice' in tx: gas_price = to_int_if_hex(tx['gasPrice']) else: gas_price = safe_min_gas_price if 'gas' in tx: gas = to_int_if_hex(tx['gas']) else: gas = GAS_TX if 'data' in tx: data = tx['data'] else: data = b'' if 'nonce' in tx: nonce = to_int_if_hex(tx['nonce']) else: nonce = None transactions[i]['nonce'] = nonce signed_tx = chain.create_and_sign_transaction_for_queue_block( gas_price=gas_price, gas=gas, to=decode_hex(tx['to']), value=to_int_if_hex(tx['value']), data=data, nonce=nonce, v=0, r=0, s=0) signed_transactions.append(signed_tx) block = chain.import_current_queue_block() average_block_gas_price = get_block_average_transaction_gas_price( block) if average_block_gas_price < min_gas_price: raise Exception( "The average gas price of all transactions in your block does not meet the required minimum gas price. Your average block gas price: {}. Min gas price: {}" .format(average_block_gas_price, min_gas_price)) if len(signed_transactions) == 0 and len( block.receive_transactions) == 0: raise BaseRPCError( "Cannot send block if it has no send or receive transactions." ) self._event_bus.broadcast( NewBlockEvent(block=cast(P2PBlock, block), from_rpc=True)) send_transaction_hashes = [ encode_hex(tx.hash) for tx in signed_transactions ] receive_transaction_hashes = [ encode_hex(tx.hash) for tx in block.receive_transactions ] all_transaction_hashes = send_transaction_hashes all_transaction_hashes.extend(receive_transaction_hashes) if not include_receive: return all_transaction_hashes[0] else: return all_transaction_hashes
async def _send_transactions(self, transactions, account): async with self._importing_block_lock: print("Importing block") normalized_wallet_address = to_normalized_address(account.address) wallet_address_hex = account.address wallet_address = decode_hex(wallet_address_hex) chain = self.get_new_chain(Address(wallet_address), account._key_obj) # make the chain read only for creating the block. We don't want to actually import it here. chain.enable_read_only_db() signed_transactions = [] for i in range(len(transactions)): tx = transactions[i] if to_normalized_address(tx['from']) != normalized_wallet_address: raise BaseRPCError("When sending multiple transactions at once, they must all be from the same address") if 'gasPrice' in tx: gas_price = tx['gasPrice'] else: gas_price = to_wei(chain.chaindb.get_required_block_min_gas_price()+1, 'gwei') if 'gas' in tx: gas = tx['gas'] else: gas = GAS_TX if 'data' in tx: data = tx['data'] else: data = b'' if 'nonce' in tx: nonce = tx['nonce'] else: if normalized_wallet_address in self._current_tx_nonce: nonce_from_state = chain.get_vm().state.account_db.get_nonce(wallet_address) nonce = chain.get_vm().state.account_db.get_nonce(wallet_address) transactions[i]['nonce'] = nonce signed_tx = chain.create_and_sign_transaction_for_queue_block( gas_price=gas_price, gas=GAS_TX, to=decode_hex(tx['to']), value=to_int_if_hex(tx['value']), data=data, v=0, r=0, s=0 ) signed_transactions.append(signed_tx) # We put this part down here because we have to return the hashes of all the transactions. # Check when the next block can be. If we are early, queue up the tx. allowed_time_of_next_block = chain.get_allowed_time_of_next_block() min_time_between_blocks = chain.min_time_between_blocks now = int(time.time()) # Or, we may have just sent a block and it hasn't been imported yet. Lets check. Then send to queue if that is the case. if normalized_wallet_address in self._newest_block_times_sent_to_event_bus: if self._newest_block_times_sent_to_event_bus[normalized_wallet_address] >= allowed_time_of_next_block: allowed_time_of_next_block = self._newest_block_times_sent_to_event_bus[normalized_wallet_address] + min_time_between_blocks if now < allowed_time_of_next_block: for tx in transactions: await self._add_transaction_to_queue(tx) asyncio.ensure_future(self._import_delayed_block_with_queue(account, allowed_time_of_next_block-now, min_time_between_blocks)) else: block = chain.import_current_queue_block() self._event_bus.broadcast( NewBlockEvent(block=cast(P2PBlock, block), from_rpc=True) ) self._newest_block_times_sent_to_event_bus[normalized_wallet_address] = block.header.timestamp if len(signed_transactions) == 1: return encode_hex(signed_transactions[0].hash) else: return [encode_hex(tx.hash) for tx in signed_transactions]
async def _get_all_account_addresses_set_from_cache(self): if not self._account_address_cache_ready.is_set(): raise BaseRPCError("Account cache is still building. Please wait and try again in a moment.") return self._account_address_cache
async def sendRawBlock(self, encoded_micro_block): chain = self.get_new_chain() encoded_micro_block = decode_hex(encoded_micro_block) micro_block = rlp.decode(encoded_micro_block, sedes=chain.get_vm().micro_block_class) block_class = self._chain_class.get_vm_class_for_block_timestamp( timestamp=micro_block.header.timestamp).get_block_class() full_block = block_class.from_micro_block(micro_block) min_time_between_blocks = chain.get_vm( header=full_block.header).min_time_between_blocks # Validate the block here if (full_block.header.timestamp < (int(time.time()) - MAX_ALLOWED_AGE_OF_NEW_RPC_BLOCK)): raise BaseRPCError( "The block timestamp is to old. We can only import new blocks over RPC." ) if (full_block.header.timestamp > int(time.time() + BLOCK_TIMESTAMP_FUTURE_ALLOWANCE)): raise BaseRPCError( "The block timestamp is in the future and cannot be accepted. You should check your computer clock." ) try: canonical_head = chain.chaindb.get_canonical_head( full_block.header.chain_address) if canonical_head.block_number >= full_block.header.block_number: raise BaseRPCError( "You are attempting to replace an existing block. This is not allowed." ) if full_block.header.timestamp < (canonical_head.timestamp + min_time_between_blocks): raise BaseRPCError( "Not enough time has passed for you to add a new block yet. New blocks can only be added to your chain every {} seconds" .format(min_time_between_blocks)) except CanonicalHeadNotFound: pass if ((full_block.header.block_number != 0) and (not chain.chaindb.is_in_canonical_chain( full_block.header.parent_hash))): raise BaseRPCError("Parent block not found on canonical chain.") # Check our current syncing stage. Must be sync stage 4. current_sync_stage_response = await self._event_bus.request( CurrentSyncStageRequest()) if current_sync_stage_response.sync_stage < FULLY_SYNCED_STAGE_ID: raise BaseRPCError( "This node is still syncing with the network. Please wait until this node has synced and try again." ) # Check that our block import queue is not deep. It could cause min gas to increase before this block makes it there. current_block_import_queue_length = await self._event_bus.request( BlockImportQueueLengthRequest()) if current_block_import_queue_length.queue_length > MAX_ALLOWED_LENGTH_BLOCK_IMPORT_QUEUE: raise BaseRPCError( "This node's block import queue is saturated. Please wait a moment and try again." ) # Make sure it meets the local min gas price if not does_block_meet_min_gas_price(full_block, chain): required_min_gas_price = self._chain.min_gas_db.get_required_block_min_gas_price( ) raise Exception( "Block transactions don't meet the minimum gas price requirement of {}" .format(required_min_gas_price)) # Make sure it meets the average min gas price of the network. If it doesnt, then it won't reach consensus. current_network_min_gas_price_response = await self._event_bus.request( AverageNetworkMinGasPriceRequest()) network_min_gas_price_wei = to_wei( current_network_min_gas_price_response.min_gas_price, 'gwei') if network_min_gas_price_wei > get_block_average_transaction_gas_price( full_block): raise BaseRPCError( "This block doesn't meet the minimum gas requirements to reach consensus on the network. It must have at least {} average gas price on all transactions." .format(current_network_min_gas_price_response.min_gas_price)) self._event_bus.broadcast( NewBlockEvent(block=cast(P2PBlock, full_block), from_rpc=True)) return True
async def _send_transactions(self, transactions, account): async with self._importing_block_lock: print("Importing block") normalized_wallet_address = to_normalized_address(account.address) wallet_address_hex = account.address wallet_address = decode_hex(wallet_address_hex) chain = self.get_new_chain(Address(wallet_address), account._key_obj) allowed_time_of_next_block = chain.get_allowed_time_of_next_block() now = int(time.time()) if now < allowed_time_of_next_block: raise BaseRPCError( "The minimum time between blocks has not passed. You must wait until {} to send the next block. " "Use personal_sendTrasactions to send multiple transactions at once." .format(allowed_time_of_next_block)) # make the chain read only for creating the block. We don't want to actually import it here. chain.enable_read_only_db() signed_transactions = [] for i in range(len(transactions)): tx = transactions[i] if to_normalized_address( tx['from']) != normalized_wallet_address: raise BaseRPCError( "When sending multiple transactions at once, they must all be from the same address" ) if 'gasPrice' in tx: gas_price = tx['gasPrice'] else: gas_price = to_wei( chain.chaindb.get_required_block_min_gas_price() + 1, 'gwei') if 'gas' in tx: gas = tx['gas'] else: gas = GAS_TX if 'data' in tx: data = tx['data'] else: data = b'' if 'nonce' in tx: nonce = tx['nonce'] else: nonce = None transactions[i]['nonce'] = nonce signed_tx = chain.create_and_sign_transaction_for_queue_block( gas_price=gas_price, gas=GAS_TX, to=decode_hex(tx['to']), value=to_int_if_hex(tx['value']), data=data, nonce=nonce, v=0, r=0, s=0) signed_transactions.append(signed_tx) block = chain.import_current_queue_block() self._event_bus.broadcast( NewBlockEvent(block=cast(P2PBlock, block), from_rpc=True)) if len(signed_transactions) == 1: return encode_hex(signed_transactions[0].hash) else: return [encode_hex(tx.hash) for tx in signed_transactions]