def from_proto( cls, item: tx_pb_v4.HistoryItem, state: tx_pb_v4.GetTransactionResponse.State ) -> 'TransactionData': payments = [] if item.invoice_list and item.invoice_list.invoices: if len(item.payments) != len(item.invoice_list.invoices): raise ValueError('number of invoices does not match number of payments') il = InvoiceList.from_proto(item.invoice_list) else: il = None tx_type = TransactionType.UNKNOWN memo = None tx_errors = None if item.solana_transaction.value: solana_tx = solana.Transaction.unmarshal(item.solana_transaction.value) program_idx = solana_tx.message.instructions[0].program_index if solana_tx.message.accounts[program_idx] == solana.MEMO_PROGRAM_KEY: decompiled_memo = solana.decompile_memo(solana_tx.message, 0) memo_data = decompiled_memo.data.decode('utf-8') try: agora_memo = AgoraMemo.from_b64_string(memo_data) tx_type = agora_memo.tx_type() except ValueError: memo = memo_data tx_errors = TransactionErrors.from_solana_tx(solana_tx, item.transaction_error, item.transaction_id.value) elif item.stellar_transaction.envelope_xdr: env = te.TransactionEnvelope.from_xdr(base64.b64encode(item.stellar_transaction.envelope_xdr)) tx = env.tx if isinstance(tx.memo, stellar_memo.HashMemo): try: agora_memo = AgoraMemo.from_base_memo(tx.memo) tx_type = agora_memo.tx_type() except ValueError: pass elif isinstance(tx.memo, stellar_memo.TextMemo): memo = tx.memo.text.decode() tx_errors = TransactionErrors.from_stellar_tx(env, item.transaction_error, item.transaction_id.value) for idx, p in enumerate(item.payments): inv = il.invoices[idx] if il and il.invoices else None payments.append(ReadOnlyPayment(PublicKey(p.source.value), PublicKey(p.destination.value), tx_type, p.amount, invoice=inv, memo=memo)) return cls( item.transaction_id.value, TransactionState.from_proto_v4(state), payments, error=tx_errors, )
def test_errors_from_solana_tx(self, instruction_index, exp_op_index, exp_payment_index): keys = [pk.public_key for pk in generate_keys(4)] tx = solana.Transaction.new( keys[0], [ memo.memo_instruction('data'), token.transfer(keys[1], keys[2], keys[1], 100), token.set_authority(keys[1], keys[1], token.AuthorityType.CLOSE_ACCOUNT, keys[3]) ] ) tx_id = b'tx_sig' errors = TransactionErrors.from_solana_tx(tx, model_pbv4.TransactionError( reason=model_pbv4.TransactionError.Reason.INSUFFICIENT_FUNDS, instruction_index=instruction_index, ), tx_id) assert isinstance(errors.tx_error, InsufficientBalanceError) assert len(errors.op_errors) == 3 for i in range(0, len(errors.op_errors)): if i == exp_op_index: assert isinstance(errors.op_errors[i], InsufficientBalanceError) else: assert not errors.op_errors[i] if exp_payment_index > -1: assert len(errors.payment_errors) == 1 for i in range(0, len(errors.payment_errors)): if i == exp_payment_index: assert isinstance(errors.payment_errors[i], InsufficientBalanceError) else: assert not errors.payment_errors[i] else: assert not errors.payment_errors
def test_errors_from_stellar_tx(self, instruction_index, exp_op_index, exp_payment_index): acc1 = gen_account_id() acc2 = gen_account_id() operations = [ gen_create_op(acc1, acc2), gen_payment_op(acc2, amount=15), gen_payment_op(acc1, amount=15), gen_create_op(acc1, acc2), ] memo = AgoraMemo.new(1, TransactionType.EARN, 1, b'') hash_memo = gen_hash_memo(memo.val) envelope_xdr = gen_tx_envelope_xdr(acc1, 1, operations, hash_memo) env = te.TransactionEnvelope.from_xdr(base64.b64encode(envelope_xdr)) errors = TransactionErrors.from_stellar_tx(env, model_pbv4.TransactionError( reason=model_pbv4.TransactionError.Reason.INSUFFICIENT_FUNDS, instruction_index=instruction_index, ), b'tx_hash') assert isinstance(errors.tx_error, InsufficientBalanceError) assert len(errors.op_errors) == 4 for i in range(0, len(errors.op_errors)): if i == exp_op_index: assert isinstance(errors.op_errors[i], InsufficientBalanceError) else: assert not errors.op_errors[i] if exp_payment_index > -1: assert len(errors.payment_errors) == 2 for i in range(0, len(errors.payment_errors)): if i == exp_payment_index: assert isinstance(errors.payment_errors[i], InsufficientBalanceError) else: assert not errors.payment_errors[i] else: assert not errors.payment_errors
def _submit(): nonlocal attempt attempt += 1 req = tx_pb_v4.SubmitTransactionRequest( transaction=model_pb_v4.Transaction(value=tx_bytes, ), invoice_list=invoice_list.to_proto() if invoice_list else None, commitment=commitment.to_proto(), ) resp = self._transaction_stub_v4.SubmitTransaction( req, metadata=self._metadata, timeout=_GRPC_TIMEOUT_SECONDS) if resp.result == tx_pb_v4.SubmitTransactionResponse.Result.REJECTED: raise TransactionRejectedError() if resp.result == tx_pb_v4.SubmitTransactionResponse.Result.PAYER_REQUIRED: raise PayerRequiredError() result = SubmitTransactionResult(tx_id=resp.signature.value) if resp.result == tx_pb_v4.SubmitTransactionResponse.Result.ALREADY_SUBMITTED: # If this occurs on the first attempt, it's likely due to the submission of two identical transactions # in quick succession and we should raise the error to the caller. Otherwise, it's likely that the # transaction completed successfully on a previous attempt that failed due to a transient error. if attempt == 1: raise AlreadySubmittedError() elif resp.result == tx_pb_v4.SubmitTransactionResponse.Result.FAILED: result.tx_error = TransactionErrors.from_proto_error( resp.transaction_error) elif resp.result == tx_pb_v4.SubmitTransactionResponse.Result.INVOICE_ERROR: result.invoice_errors = resp.invoice_errors elif resp.result != tx_pb_v4.SubmitTransactionResponse.Result.OK: raise Error(f'unexpected result from agora: {resp.result}') return result
def _submit(): req = tx_pb_v3.SubmitTransactionRequest( envelope_xdr=tx_bytes, invoice_list=invoice_list.to_proto() if invoice_list else None, ) try: resp = self._transaction_stub_v3.SubmitTransaction( req, metadata=self._metadata, timeout=_GRPC_TIMEOUT_SECONDS) except grpc.RpcError as e: raise BlockchainVersionError() if self._is_migration_error( e) else e result = SubmitTransactionResult(tx_id=resp.hash.value) if resp.result == tx_pb_v3.SubmitTransactionResponse.Result.REJECTED: raise TransactionRejectedError() elif resp.result == tx_pb_v3.SubmitTransactionResponse.Result.INVOICE_ERROR: result.invoice_errors = resp.invoice_errors elif resp.result == tx_pb_v3.SubmitTransactionResponse.Result.FAILED: result.tx_error = TransactionErrors.from_result( resp.result_xdr) elif resp.result != tx_pb_v3.SubmitTransactionResponse.Result.OK: raise Error(f'unexpected result from agora: {resp.result}') return result
def test_error_from_result_other_op(self): op_result = gen_merge_op_result(xdr_const.ACCOUNT_MERGE_MALFORMED) result_xdr = gen_result_xdr(xdr_const.txFAILED, [op_result]) te = TransactionErrors.from_result(result_xdr) assert isinstance(te.tx_error, Error) assert isinstance(te.op_errors[0], Error)
def from_proto(cls, item: tx_pb.HistoryItem) -> 'TransactionData': data = cls( item.hash.value, error=TransactionErrors.from_result(item.result_xdr), ) if item.envelope_xdr: env = te.TransactionEnvelope.from_xdr( base64.b64encode(item.envelope_xdr)) data.payments = ReadOnlyPayment.payments_from_envelope( env, item.invoice_list) return data
def test_error_from_result_multi_op(self): op_results = [ gen_create_op_result(xdr_const.CREATE_ACCOUNT_SUCCESS), gen_create_op_result(xdr_const.CREATE_ACCOUNT_MALFORMED), gen_payment_op_result(xdr_const.PAYMENT_UNDERFUNDED), ] result_xdr = gen_result_xdr(xdr_const.txFAILED, op_results) te = TransactionErrors.from_result(result_xdr) assert isinstance(te.tx_error, Error) assert not te.op_errors[0] assert isinstance(te.op_errors[1], TransactionMalformedError) assert isinstance(te.op_errors[2], InsufficientBalanceError)
def test_from_result_tx_failed(self, tx_result_code: int, op_type: int, op_result_code: int, op_error_type: type): """ Tests conversion of error types with transaction results containing only one operation result. """ if op_type == xdr_const.CREATE_ACCOUNT: op_result = gen_create_op_result(op_result_code) elif op_type == xdr_const.PAYMENT: op_result = gen_payment_op_result(op_result_code) else: raise ValueError('invalid op_type') result_xdr = gen_result_xdr(tx_result_code, [op_result] if op_result else []) te = TransactionErrors.from_result(result_xdr) assert isinstance(te.tx_error, Error) assert isinstance(te.op_errors[0], op_error_type)
def _submit(): req = tx_pb.SubmitTransactionRequest( envelope_xdr=tx_bytes, invoice_list=invoice_list.to_proto() if invoice_list else None, ) resp = self.transaction_stub.SubmitTransaction( req, timeout=_GRPC_TIMEOUT_SECONDS) result = SubmitStellarTransactionResult(tx_hash=resp.hash.value) if resp.result == tx_pb.SubmitTransactionResponse.Result.REJECTED: raise TransactionRejectedError() elif resp.result == tx_pb.SubmitTransactionResponse.Result.INVOICE_ERROR: result.invoice_errors = resp.invoice_errors elif resp.result == tx_pb.SubmitTransactionResponse.Result.FAILED: result.tx_error = TransactionErrors.from_result( resp.result_xdr) elif resp.result != tx_pb.SubmitTransactionResponse.Result.OK: raise Error("unexpected result from agora: {}".format( resp.result)) return result
def test_error_from_result_no_op(self, tx_result_code: int, exception_type: type): result_xdr = gen_result_xdr(tx_result_code, []) assert isinstance( TransactionErrors.from_result(result_xdr).tx_error, exception_type)
def test_from_result_success(self): op_result = gen_create_op_result(xdr_const.CREATE_ACCOUNT_SUCCESS) result_xdr = gen_result_xdr(xdr_const.txSUCCESS, [op_result] if op_result else []) assert not TransactionErrors.from_result(result_xdr)