Beispiel #1
0
 async def create_and_broadcast(self, request):
     account = None
     tx = None
     try:
         tx, account, password = await self._create_tx_helper(request)
         try:
             result = await self._broadcast_transaction(
                 str(tx), tx.hash(), account)
         except aiorpcx.jsonrpc.RPCError as e:
             raise Fault(Errors.AIORPCX_ERROR_CODE, e.message)
         self.prev_transaction = result
         response = {"txid": result}
         self.logger.debug("successful broadcast for %s", result)
         return good_response(response)
     except Fault as e:
         if tx and tx.is_complete(
         ) and e.code != Errors.ALREADY_SENT_TRANSACTION_CODE:
             self.cleanup_tx(tx, account)
         return fault_to_http_response(e)
     except Exception as e:
         self.logger.exception(
             "unexpected error in create_and_broadcast handler")
         if tx and tx.is_complete() and not (
                 isinstance(e, AssertionError)
                 and str(e) == 'duplicate set not supported'):
             self.cleanup_tx(tx, account)
         return fault_to_http_response(
             Fault(code=Errors.GENERIC_INTERNAL_SERVER_ERROR,
                   message=str(e)))
Beispiel #2
0
 def raise_for_var_missing(self, vars, required_vars: List[str]):
     for varname in required_vars:
         if vars.get(varname) is None:
             if varname in HEADER_VARS:
                 raise Fault(Errors.HEADER_VAR_NOT_PROVIDED_CODE,
                             Errors.HEADER_VAR_NOT_PROVIDED_MESSAGE.format(varname))
             else:
                 raise Fault(Errors.BODY_VAR_NOT_PROVIDED_CODE,
                             Errors.BODY_VAR_NOT_PROVIDED_MESSAGE.format(varname))
Beispiel #3
0
 def _get_wallet_path(self, wallet_name: str) -> str:
     """returns parent wallet path. The wallet_name must include .sqlite extension"""
     wallet_path = os.path.join(self.wallets_path, wallet_name)
     wallet_path = os.path.normpath(wallet_path)
     if wallet_name != os.path.basename(wallet_path):
         raise Fault(Errors.BAD_WALLET_NAME_CODE, Errors.BAD_WALLET_NAME_MESSAGE)
     if os.path.exists(wallet_path):
         return wallet_path
     else:
         raise Fault(Errors.WALLET_NOT_FOUND_CODE, Errors.WALLET_NOT_FOUND_MESSAGE)
Beispiel #4
0
    def check_if_wallet_exists(self, file_path):
        if os.path.exists(file_path):
            raise Fault(code=Errors.BAD_WALLET_NAME_CODE,
                        message=f"'{file_path + DATABASE_EXT}' already exists")

        if not file_path.endswith(DATABASE_EXT):
            if os.path.exists(file_path + DATABASE_EXT):
                raise Fault(
                    code=Errors.BAD_WALLET_NAME_CODE,
                    message=f"'{file_path + DATABASE_EXT}' already exists")
Beispiel #5
0
 def raise_for_wallet_availability(self,
                                   wallet_name: str) -> Union[str, Fault]:
     if not self._wallet_name_available(wallet_name):
         raise Fault(
             code=Errors.WALLET_NOT_FOUND_CODE,
             message=Errors.WALLET_NOT_FOUND_MESSAGE.format(wallet_name))
     return wallet_name
Beispiel #6
0
    async def _create_tx_helper(self, request) -> Union[Tuple, Fault]:
        try:
            vars = await self.argparser(request)
            self.raise_for_var_missing(vars, required_vars=[VNAME.WALLET_NAME, VNAME.ACCOUNT_ID,
                                                            VNAME.OUTPUTS, VNAME.PASSWORD])
            wallet_name = vars[VNAME.WALLET_NAME]
            index = vars[VNAME.ACCOUNT_ID]
            outputs = vars[VNAME.OUTPUTS]

            utxos = vars.get(VNAME.UTXOS, None)
            utxo_preselection = vars.get(VNAME.UTXO_PRESELECTION, True)
            password = vars.get(VNAME.PASSWORD, None)

            child_wallet = self._get_account(wallet_name, index)

            if not utxos:
                exclude_frozen = vars.get(VNAME.EXCLUDE_FROZEN, True)
                confirmed_only = vars.get(VNAME.CONFIRMED_ONLY, False)
                mature = vars.get(VNAME.MATURE, True)
                utxos = child_wallet.get_utxos(exclude_frozen=exclude_frozen,
                                               confirmed_only=confirmed_only, mature=mature)

            if utxo_preselection:  # Defaults to True
                utxos = self.preselect_utxos(utxos, outputs)

            # Todo - loop.run_in_executor
            tx = child_wallet.make_unsigned_transaction(utxos, outputs, self.app_state.config)
            return tx, child_wallet, password
        except NotEnoughFunds:
            raise Fault(Errors.INSUFFICIENT_COINS_CODE, Errors.INSUFFICIENT_COINS_MESSAGE)
Beispiel #7
0
    async def broadcast(self, request):
        """Broadcast a rawtx (hex string) to the network. """
        try:
            required_vars = [VNAME.WALLET_NAME, VNAME.ACCOUNT_ID, VNAME.RAWTX]
            vars = await self.argparser(request, required_vars=required_vars)
            wallet_name = vars[VNAME.WALLET_NAME]
            index = vars[VNAME.ACCOUNT_ID]
            rawtx = vars[VNAME.RAWTX]

            account = self._get_account(wallet_name, index)
            tx = Transaction.from_hex(rawtx)
            self.raise_for_duplicate_tx(tx)
            frozen_utxos = self.app_state.app.get_and_set_frozen_utxos_for_tx(
                tx, account)
            result = await self._broadcast_transaction(rawtx, tx.hash(),
                                                       account)
            self.prev_transaction = result
            response = {"value": {"txid": result}}
            return good_response(response)
        except Fault as e:
            return fault_to_http_response(e)
        except aiorpcx.jsonrpc.RPCError as e:
            account.set_frozen_coin_state(frozen_utxos, False)
            self.remove_signed_transaction(tx, account)
            return fault_to_http_response(
                Fault(Errors.AIORPCX_ERROR_CODE, e.message))
Beispiel #8
0
 def raise_for_duplicate_tx(self, tx):
     """because the network can be very slow to give this important feedback and instead will
     return the txid as an http 200 response."""
     if tx.txid() == self.prev_transaction:
         message = "You've already sent this transaction: {}".format(tx.txid())
         fault = Fault(Errors.ALREADY_SENT_TRANSACTION_CODE, message)
         raise fault
     return
Beispiel #9
0
 def _fetch_transaction_dto(self, account: AbstractAccount,
                            tx_id) -> Optional[Dict]:
     tx_hash = hex_str_to_hash(tx_id)
     tx = account.get_transaction(tx_hash)
     if not tx:
         raise Fault(Errors.TRANSACTION_NOT_FOUND_CODE,
                     Errors.TRANSACTION_NOT_FOUND_MESSAGE)
     return {"tx_hex": tx.to_hex()}
Beispiel #10
0
 def remove_transaction(self, tx_hash: bytes, wallet: AbstractAccount):
     # removal of txs that are not in the StateSigned tx state is disabled for now as it may
     # cause issues with expunging utxos inadvertently.
     try:
         tx = wallet.get_transaction(tx_hash)
         tx_flags = wallet._wallet._transaction_cache.get_flags(tx_hash)
         is_signed_state = (tx_flags
                            & TxFlags.StateSigned) == TxFlags.StateSigned
         # Todo - perhaps remove restriction to StateSigned only later (if safe for utxos state)
         if tx and is_signed_state:
             wallet.delete_transaction(tx_hash)
         if tx and not is_signed_state:
             raise Fault(Errors.DISABLED_FEATURE_CODE,
                         Errors.DISABLED_FEATURE_MESSAGE)
     except MissingRowError:
         raise Fault(Errors.TRANSACTION_NOT_FOUND_CODE,
                     Errors.TRANSACTION_NOT_FOUND_MESSAGE)
Beispiel #11
0
 def _get_account(self, wallet_name: str, account_id: int=1) \
         -> Union[Fault, AbstractAccount]:
     parent_wallet = self._get_parent_wallet(wallet_name=wallet_name)
     try:
         child_wallet = parent_wallet.get_account(account_id)
     except KeyError:
         message = f"There is no account at account_id: {account_id}."
         raise Fault(Errors.WALLET_NOT_FOUND_CODE, message)
     return child_wallet
Beispiel #12
0
 def _get_parent_wallet(self, wallet_name: str) -> Wallet:
     """returns a child wallet object"""
     path_result = self._get_wallet_path(wallet_name)
     parent_wallet = self.app_state.daemon.get_wallet(path_result)
     if not parent_wallet:
         message = Errors.LOAD_BEFORE_GET_MESSAGE.format(
             get_network_type(), 'wallet_name.sqlite')
         raise Fault(code=Errors.LOAD_BEFORE_GET_CODE, message=message)
     return parent_wallet
Beispiel #13
0
    async def _load_wallet(self, wallet_name: Optional[str] = None) -> Union[Fault, Wallet]:
        """Loads one parent wallet into the daemon and begins synchronization"""
        if not wallet_name.endswith(".sqlite"):
            wallet_name += ".sqlite"

        path_result = self._get_wallet_path(wallet_name)
        parent_wallet = self.app_state.daemon.load_wallet(path_result)
        if parent_wallet is None:
            raise Fault(Errors.WALLET_NOT_LOADED_CODE,
                         Errors.WALLET_NOT_LOADED_MESSAGE)
        return parent_wallet
        async def main():
            async with aiohttp.ClientSession() as session:
                tasks = [
                    asyncio.create_task(self.create_and_send(
                        session, payload2)) for _ in range(0, n_txs)
                ]
                results = await asyncio.gather(*tasks, return_exceptions=True)

            for result in results:
                error_code = result.get('code')
                if error_code:
                    assert False, str(Fault(error_code, result.get('message')))
Beispiel #15
0
 async def create_tx(self, request):
     """
     General purpose transaction builder.
     - Should handle any kind of output script.( see bitcoinx.address for
     utilities for building p2pkh, multisig etc outputs as hex strings.)
     """
     account = None
     tx = None
     try:
         tx, account, password = await self._create_tx_helper(request)
         response = {"txid": tx.txid(), "rawtx": str(tx)}
         return good_response(response)
     except Fault as e:
         if tx and tx.is_complete() and e.code != Fault(
                 Errors.ALREADY_SENT_TRANSACTION_CODE):
             self.cleanup_tx(tx, account)
         return fault_to_http_response(e)
     except Exception as e:
         if tx and tx.is_complete():
             self.cleanup_tx(tx, account)
         return fault_to_http_response(
             Fault(code=Errors.GENERIC_INTERNAL_SERVER_ERROR,
                   message=str(e)))
Beispiel #16
0
 async def create_and_broadcast(self, request):
     try:
         tx, account, password = await self._create_tx_helper(request)
         self.raise_for_duplicate_tx(tx)
         account.sign_transaction(tx, password)
         frozen_utxos = self.app_state.app.get_and_set_frozen_utxos_for_tx(
             tx, account)
         result = await self._broadcast_transaction(str(tx), tx.hash(),
                                                    account)
         self.prev_transaction = result
         response = {"value": {"txid": result}}
         self.logger.debug("successful broadcast for %s", result)
         return good_response(response)
     except Fault as e:
         return fault_to_http_response(e)
     except aiorpcx.jsonrpc.RPCError as e:
         account.set_frozen_coin_state(frozen_utxos, False)
         self.remove_signed_transaction(tx, account)
         return fault_to_http_response(
             Fault(Errors.AIORPCX_ERROR_CODE, e.message))
Beispiel #17
0
def test_fault_to_http_response():

    fault_negative = Fault(-1, '')
    fault_zero = Fault(0, '<message>')
    fault_4xx = Fault(40000, '<message>')
    fault_404 = Fault(40400, '<not found message>')
    fault_5xx = Fault(50000, '<message>')
    fault_other = Fault(60000, '<message>')
    assert fault_to_http_response(fault_negative)._body == \
           bad_request(fault_negative.code, fault_negative.message)._body
    assert fault_to_http_response(fault_zero)._body == \
           bad_request(fault_zero.code, fault_zero.message)._body
    assert fault_to_http_response(fault_4xx)._body == \
           bad_request(fault_4xx.code, fault_4xx.message)._body
    assert fault_to_http_response(fault_404)._body == \
           not_found(fault_404.code, fault_404.message)._body
    assert fault_to_http_response(fault_5xx)._body == \
           internal_server_error(fault_5xx.code, fault_5xx.message)._body
    assert fault_to_http_response(fault_other)._body == \
           bad_request(fault_other.code, fault_other.message)._body
Beispiel #18
0
 def account_id_if_isdigit(self, index: str) -> Union[int, Fault]:
     if not index.isdigit():
         message = "child wallet index in url must be an integer. You tried " \
                   "index='%s'." % index
         raise Fault(code=Errors.GENERIC_BAD_REQUEST_CODE, message=message)
     return int(index)
Beispiel #19
0
    async def split_utxos(self, request) -> Union[Fault, Any]:
        account = None
        tx = None
        try:
            required_vars = [
                VNAME.WALLET_NAME, VNAME.ACCOUNT_ID, VNAME.SPLIT_COUNT,
                VNAME.PASSWORD
            ]
            vars = await self.argparser(request, required_vars=required_vars)
            wallet_name = vars[VNAME.WALLET_NAME]
            account_id = vars[VNAME.ACCOUNT_ID]
            split_count = vars[VNAME.SPLIT_COUNT]

            # optional
            split_value = vars.get(VNAME.SPLIT_VALUE, 10000)
            password = vars.get(VNAME.PASSWORD, None)
            desired_utxo_count = vars.get(VNAME.DESIRED_UTXO_COUNT, 2000)
            require_confirmed = vars.get(VNAME.REQUIRE_CONFIRMED, False)

            account = self._get_account(wallet_name, account_id)

            # Approximate size of a transaction with one P2PKH input and one P2PKH output.
            base_fee = self.app_state.config.estimate_fee(203)
            loop = asyncio.get_event_loop()
            # run in thread - CPU intensive code
            partial_coin_selection = partial(
                self.select_inputs_and_outputs,
                self.app_state.config,
                account,
                base_fee,
                split_count=split_count,
                desired_utxo_count=desired_utxo_count,
                require_confirmed=require_confirmed,
                split_value=split_value)

            split_result = await loop.run_in_executor(self.txb_executor,
                                                      partial_coin_selection)
            if isinstance(split_result, Fault):
                return fault_to_http_response(split_result)
            self.logger.debug("split result: %s", split_result)
            utxos, outputs, attempted_split = split_result
            if not attempted_split:
                fault = Fault(Errors.SPLIT_FAILED_CODE,
                              Errors.SPLIT_FAILED_MESSAGE)
                return fault_to_http_response(fault)
            tx = account.make_unsigned_transaction(utxos, outputs,
                                                   self.app_state.config)
            account.sign_transaction(tx, password)
            self.raise_for_duplicate_tx(tx)

            # broadcast
            result = await self._broadcast_transaction(str(tx), tx.hash(),
                                                       account)
            self.prev_transaction = result
            response = {"txid": result}
            return good_response(response)
        except Fault as e:
            if tx and tx.is_complete() and e.code != Fault(
                    Errors.ALREADY_SENT_TRANSACTION_CODE):
                self.cleanup_tx(tx, account)
            return fault_to_http_response(e)
        except InsufficientCoinsError as e:
            self.logger.debug(Errors.INSUFFICIENT_COINS_MESSAGE)
            self.logger.debug("utxos remaining: %s", account.get_utxos())
            return fault_to_http_response(
                Fault(Errors.INSUFFICIENT_COINS_CODE,
                      Errors.INSUFFICIENT_COINS_MESSAGE))
        except Exception as e:
            if tx and tx.is_complete():
                self.cleanup_tx(tx, account)
            return fault_to_http_response(
                Fault(code=Errors.GENERIC_INTERNAL_SERVER_ERROR,
                      message=str(e)))
Beispiel #20
0
 def raise_for_rawtx_size(self, rawtx):
     if (len(rawtx) / 2) > 99000:
         fault = Fault(Errors.DATA_TOO_BIG_CODE,
                       Errors.DATA_TOO_BIG_MESSAGE)
         raise fault
Beispiel #21
0
 async def get_body_vars(self, request) -> Dict:
     try:
         return await decode_request_body(request)
     except JSONDecodeError as e:
         message = "JSONDecodeError " + str(e)
         raise Fault(Errors.JSON_DECODE_ERROR_CODE, message)
Beispiel #22
0
def _fake_remove_transaction_raise_fault(tx_hash: bytes, wallet: AbstractAccount):
    raise Fault(Errors.DISABLED_FEATURE_CODE, Errors.DISABLED_FEATURE_MESSAGE)
Beispiel #23
0
 def _fake_create_tx_helper_raise_exception(self, request) -> Tuple[Any, set]:
     raise Fault(Errors.INSUFFICIENT_COINS_CODE, Errors.INSUFFICIENT_COINS_MESSAGE)
Beispiel #24
0
 def raise_for_type_okay(self, vars):
     for vname in vars:
         if vars.get(vname, None):
             if not isinstance(vars.get(vname), ARGTYPES.get(vname)):
                 message = f"{vars.get(vname)} must be of type: '{ARGTYPES.get(vname)}'"
                 raise Fault(Errors.GENERIC_BAD_REQUEST_CODE, message)