def fill( self, counter: Optional[int] = None, ttl: Optional[int] = None, **kwargs ) -> 'OperationGroup': """Try to fill all fields left unfilled, use approximate fees (not optimal, use `autofill` to simulate operation and get precise values). :param counter: Override counter value (for manual handling) :param ttl: Number of blocks to wait in the mempool before removal (default is 5 for public network, 60 for sandbox) :rtype: OperationGroup """ if kwargs.get('branch_offset') is not None: logger.warning('`branch_offset` argument is deprecated, use `ttl` instead') ttl = MAX_OPERATIONS_TTL - kwargs['branch_offset'] if ttl is None: ttl = self.context.get_operations_ttl() if not 0 < ttl <= MAX_OPERATIONS_TTL: raise Exception('`ttl` has to be in range (0, 60]') chain_id = self.chain_id or self.context.get_chain_id() branch = self.branch or self.shell.blocks[f'head-{MAX_OPERATIONS_TTL - ttl}'].hash() protocol = self.protocol or self.shell.head.header()['protocol'] source = self.key.public_key_hash() if counter is not None: self.context.set_counter(counter) replace_map = { 'pkh': source, 'source': source, 'delegate': source, # self registration 'counter': lambda x: str(self.context.get_counter()), 'secret': lambda x: self.key.activation_code, 'period': lambda x: str(self.shell.head.voting_period()), 'public_key': lambda x: self.key.public_key(), 'fee': lambda x: str(default_fee(x)), 'gas_limit': lambda x: str(default_gas_limit(x, self.context.constants)), 'storage_limit': lambda x: str(default_storage_limit(x, self.context.constants)), } def fill_content(content): content = content.copy() for k, v in replace_map.items(): if content.get(k) in ['', '0']: content[k] = v(content) if callable(v) else v return content return self._spawn( contents=list(map(fill_content, self.contents)), protocol=protocol, chain_id=chain_id, branch=branch, )
def wrapper(*args, **kwargs): for attempt in range(REQUEST_RETRY_COUNT): logger.debug('Node request attempt %s/%s', attempt + 1, REQUEST_RETRY_COUNT) try: return fn(*args, **kwargs) except requests.exceptions.ConnectionError as e: if attempt + 1 == REQUEST_RETRY_COUNT: raise e logger.warning(e) time.sleep(REQUEST_RETRY_SLEEP)
def autofill( self, gas_reserve: int = DEFAULT_GAS_RESERVE, burn_reserve: int = DEFAULT_BURN_RESERVE, counter: Optional[int] = None, ttl: Optional[int] = None, fee: Optional[int] = None, gas_limit: Optional[int] = None, storage_limit: Optional[int] = None, **kwargs, ) -> 'OperationGroup': """Fill the gaps and then simulate the operation in order to calculate fee, gas/storage limits. :param gas_reserve: Add a safe reserve for dynamically calculated gas limit (default is 100). :param burn_reserve: Add a safe reserve for dynamically calculated storage limit (default is 100). :param counter: Override counter value (for manual handling) :param ttl: Number of blocks to wait in the mempool before removal (default is 5 for public network, 60 for sandbox) :param fee: Explicitly set fee for operation. If not set fee will be calculated depending on results of operation dry-run. :param gas_limit: Explicitly set gas limit for operation. If not set gas limit will be calculated depending on results of operation dry-run. :param storage_limit: Explicitly set storage limit for operation. If not set storage limit will be calculated depending on results of operation dry-run. :rtype: OperationGroup """ if kwargs.get('branch_offset') is not None: logger.warning('`branch_offset` argument is deprecated, use `ttl` instead') ttl = MAX_OPERATIONS_TTL - kwargs['branch_offset'] opg = self.fill(counter=counter, ttl=ttl) opg_with_metadata = opg.run() if not OperationResult.is_applied(opg_with_metadata): raise RpcError.from_errors(OperationResult.errors(opg_with_metadata)) extra_size = (32 + 64) // len(opg.contents) + 1 # size of serialized branch and signature def fill_content(content: Dict[str, Any]) -> Dict[str, Any]: if validation_passes[content['kind']] == 3: _gas_limit, _storage_limit, _fee = gas_limit, storage_limit, fee if _gas_limit is None: _gas_limit = OperationResult.consumed_gas(content) if content['kind'] in ['origination', 'transaction']: _gas_limit += gas_reserve if storage_limit is None: _paid_storage_size_diff = OperationResult.paid_storage_size_diff(content) _burned = OperationResult.burned(content) _storage_limit = _paid_storage_size_diff + _burned if content['kind'] in ['origination', 'transaction']: _storage_limit += burn_reserve if _fee is None: _fee = calculate_fee(content, _gas_limit, extra_size) current_counter = int(content['counter']) content.update( gas_limit=str(_gas_limit), storage_limit=str(_storage_limit), fee=str(_fee), counter=str(current_counter + self.context.get_counter_offset()), ) content.pop('metadata') logger.debug("autofilled transaction content: %s" % content) return content opg.contents = list(map(fill_content, opg_with_metadata['contents'])) return opg
def inject( self, check_result: bool = True, num_blocks_wait: int = 5, time_between_blocks: Optional[int] = None, min_confirmations: int = 0, **kwargs ): """Inject the signed operation group. :param check_result: raise RpcError in case operation is applied but has runtime errors :param num_blocks_wait: number of blocks to wait for injection :param time_between_blocks: override the corresponding parameter from constants :param min_confirmations: number of block injections to wait for before returning :returns: operation group with metadata (raw RPC response) """ if kwargs.get('_async'): logger.warning('`_async` argument is deprecated, use `min_confirmations` instead') min_confirmations = 0 if kwargs['_async'] is True else 1 self.context.reset() opg_hash = self.shell.injection.operation.post( operation=self.binary_payload(), _async=False, ) if min_confirmations == 0: return { 'chain_id': self.chain_id, 'hash': opg_hash, **self.json_payload(), } logger.info('Waiting for %s confirmations in %s blocks', min_confirmations, num_blocks_wait) in_mempool = True confirmations = 0 for _ in range(num_blocks_wait): logger.info('Waiting for the next block') self.shell.wait_next_block(time_between_blocks=time_between_blocks) if in_mempool: try: pending_opg = self.shell.mempool.pending_operations[opg_hash] if not OperationResult.is_applied(pending_opg): raise RpcError.from_errors(OperationResult.errors(pending_opg)) logger.info('Operation %s is still in mempool', opg_hash) continue except StopIteration: in_mempool = False try: res = self.shell.blocks[-1:].find_operation(opg_hash) except StopIteration: logger.info('Operation %s not found in lastest block', opg_hash) continue if check_result: if not OperationResult.is_applied(res): raise RpcError.from_errors(OperationResult.errors(res)) confirmations += 1 logger.info('Got %s/%s confirmations', confirmations, min_confirmations) if confirmations == min_confirmations: return res raise TimeoutError(f'Operation {opg_hash} got {confirmations} confirmations in {num_blocks_wait} blocks')