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 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 _submit_request(): nonlocal attempt attempt += 1 req = tx_pb.SubmitTransactionRequest( transaction=model_pb.Transaction(value=tx_bytes, ), invoice_list=invoice_list.to_proto() if invoice_list else None, commitment=commitment.to_proto(), dedupe_id=dedupe_id, ) resp = self._transaction_stub_v4.SubmitTransaction( req, metadata=self._metadata, timeout=_GRPC_TIMEOUT_SECONDS) if resp.result == tx_pb.SubmitTransactionResponse.Result.REJECTED: raise TransactionRejectedError() if resp.result == tx_pb.SubmitTransactionResponse.Result.PAYER_REQUIRED: raise PayerRequiredError() result = SubmitTransactionResult(tx_id=resp.signature.value) if resp.result == tx_pb.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(tx_id=resp.signature.value) elif resp.result == tx_pb.SubmitTransactionResponse.Result.FAILED: result.errors = TransactionErrors.from_solana_tx( tx, resp.transaction_error, resp.signature.value) elif resp.result == tx_pb.SubmitTransactionResponse.Result.INVOICE_ERROR: result.invoice_errors = resp.invoice_errors elif resp.result != tx_pb.SubmitTransactionResponse.Result.OK: raise TransactionError( f'unexpected result from agora: {resp.result}', tx_id=resp.signature.value) return result