Skip to content

Commit

Permalink
Add SignedMessage.verification_failed_permanently/verification_explan…
Browse files Browse the repository at this point in the history
…ation

Don't attempt to re-verify Ethereum payments where verification was
known to have permanently failed.
  • Loading branch information
ryanberckmans committed Jul 26, 2024
1 parent 62cd4c7 commit 124e1ba
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 27 deletions.
10 changes: 9 additions & 1 deletion pretix_eth/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@

import pytz


def date_to_string(time_zone, date):
return date.astimezone(time_zone).date().strftime('%Y-%m-%d')


def payment_to_row(payment):
time_zone = pytz.timezone(payment.order.event.settings.timezone)
if payment.payment_date:
Expand All @@ -32,6 +34,8 @@ def payment_to_row(payment):
confirmed_transaction: SignedMessage = payment.signed_messages.last()

if confirmed_transaction is not None:
verification_failed_permanently = confirmed_transaction.verification_failed_permanently
verification_explanation = confirmed_transaction.verification_explanation
sender_address = confirmed_transaction.sender_address
recipient_address = confirmed_transaction.recipient_address
transaction_hash = confirmed_transaction.transaction_hash
Expand All @@ -46,6 +50,8 @@ def payment_to_row(payment):
token_contract_address = confirmed_transaction.token_contract_address
is_testnet = confirmed_transaction.is_testnet
else:
verification_failed_permanently = None
verification_explanation = None
sender_address = None
recipient_address = None
transaction_hash = None
Expand All @@ -68,6 +74,8 @@ def payment_to_row(payment):
date_to_string(time_zone, payment.created),
completion_date,
payment.state,
verification_failed_permanently,
verification_explanation,
fiat_amount,
primary_currency,
sender_address,
Expand Down Expand Up @@ -95,7 +103,7 @@ class EthereumOrdersExporter(ListExporter):

headers = (
'Type', 'Event slug', 'Order', 'Payment ID', 'Creation date',
'Completion date', 'Status', 'Fiat Amount', 'Currency Type',
'Completion date', 'Status', 'Verification Failed Permanently', 'Verification Explanation', 'Fiat Amount', 'Currency Type',
'Sender address', 'Receiver address',
'Transaction Hash', 'Chain ID', 'Chain Name', 'Order USD/ETH Rate',
'Receipt URL', 'Token Currency', 'Token Ticker', 'Token Name', 'Token Amount', 'Token Decimals', 'Token Contract Address', 'Is Testnet'
Expand Down
23 changes: 16 additions & 7 deletions pretix_eth/management/commands/confirm_payments.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ def handle(self, *args, **options):
log_verbosity = int(options.get("verbosity", 0))

with scope(organizer=None):
# todo change to events where pending payments are expected only?
events = Event.objects.all()
if event_slug is not None:
events = events.filter(slug=event_slug)
Expand Down Expand Up @@ -71,14 +70,16 @@ def confirm_payments_for_event(self, event: Event, no_dry_run, log_verbosity=0,

for order_payment in unconfirmed_order_payments:
try:
if log_verbosity > 0 and order_payment.state != OrderPayment.PAYMENT_STATE_CANCELED and order_payment.signed_messages.all().count() > 0:
if (log_verbosity > 0
and order_payment.state != OrderPayment.PAYMENT_STATE_CANCELED
and sum(1 for sm in order_payment.signed_messages.all() if not sm.verification_failed_permanently) > 0):
logger.info(
f" * trying to confirm payment: {order_payment} "
f"(has {order_payment.signed_messages.all().count()} signed messages)"
)
# it is tempting to put .filter(invalid=False) here, but remember
# there is still a chance that low-gas txs are mined later on.
for signed_message in order_payment.signed_messages.all():
for signed_message in [sm for sm in order_payment.signed_messages.all() if not sm.verification_failed_permanently]:
full_id = order_payment.full_id
payment_verified = False
try:
Expand All @@ -90,6 +91,7 @@ def confirm_payments_for_event(self, event: Event, no_dry_run, log_verbosity=0,
token_ticker_allowlist=["ETH", "WETH", "DAI"],
usd_per_eth=order_payment.info_data.get('usd_per_eth'),
receiver_address=signed_message.recipient_address, # WARNING recipient_address is only trusted field set from plugin config in SignedMessage. TODO consider converting recipient_address in SignedMessage to be untrusted and set this request value from a trusted receiver_address saved into info_data like all other trusted fields
external_id=full_id,
),
untrusted_to_be_verified=transfer_verification_pb2.TransferVerificationRequest.UntrustedData(
chain_id=signed_message.chain_id,
Expand All @@ -102,11 +104,18 @@ def confirm_payments_for_event(self, event: Event, no_dry_run, log_verbosity=0,
)
)

is_verified = verify_payment(transfer_verification_request)
if is_verified:
payment_verified = True
resp = verify_payment(transfer_verification_request)
if resp is not None:
if resp.is_verified:
payment_verified = True
if no_dry_run:
if resp.verification_failed_permanently:
signed_message.verification_failed_permanently = True
signed_message.verification_explanation = resp.description
signed_message.save()
except Exception as e:
logger.error(f"Error verifying payment for order: {order_payment} error: {str(e)}")
logger.error(
f"Error verifying payment for order: {order_payment} error: {str(e)}")

if payment_verified:
if no_dry_run:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 4.2.11 on 2024-07-26 19:00

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('pretix_eth', '0012_signedmessage_chain_name_signedmessage_is_testnet_and_more'),
]

operations = [
migrations.AddField(
model_name='signedmessage',
name='verification_explanation',
field=models.TextField(default=None, null=True),
),
migrations.AddField(
model_name='signedmessage',
name='verification_failed_permanently',
field=models.BooleanField(default=None, null=True),
),
]
2 changes: 2 additions & 0 deletions pretix_eth/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class SignedMessage(models.Model):
token_decimals = models.IntegerField(null=True, default=None) # number of decimals used by the transferred token. Insecure and untrusted. Provided by user's browser for admin convenience.
token_contract_address = models.TextField(null=True, default=None) # contract address of the transferred token. Undefined if transfer was a native currency. Insecure and untrusted. Provided by user's browser for admin convenience.
chain_name = models.TextField(null=True, default=None) # Name of chain on which token transfer occurred. Insecure and untrusted. Provided by user's browser for admin convenience.
verification_explanation = models.TextField(null=True, default=None) # an explanation of how the transaction verification attempt went, provided by the 3cities verifier. Only for admin convenience purposes.
verification_failed_permanently = models.BooleanField(null=True, default=None) # True if and only if verification for this transaction failed permanently and should not be retried. Must be unset if is_confirmed = true
is_testnet = models.BooleanField(null=True, default=None) # True if and only if this token transfer occurred on a testnet chain (and is therefore fake money). Insecure and untrusted. Provided by user's browser for admin convenience.
order_payment = models.ForeignKey(
to=OrderPayment,
Expand Down
9 changes: 8 additions & 1 deletion pretix_eth/payment.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ def settings_form_fields(self):
"Production - use 3cities production environment. Ie. the production interface and mainnet chains (3cities.xyz).")),
('PRODUCTION_TEST', _(
"Production Test - use 3cities production test environment. Ie. the test interface and mainnet chains (staging-prod.3cities.xyz).")),
('TEST', _("Test - use 3cities test environment. Ie. the test interface and testnet chains (staging.3cities.xyz)."))
('TEST', _(
"Test - use 3cities test environment. Ie. the test interface and testnet chains (staging.3cities.xyz)."))
],
label=_("3cities environment:"),
help_text=_(
Expand Down Expand Up @@ -242,6 +243,8 @@ def payment_control_render(self, request: HttpRequest, payment: OrderPayment):
last_signed_message: SignedMessage = payment.signed_messages.last()

if last_signed_message is not None:
verification_failed_permanently = last_signed_message.verification_failed_permanently
verification_explanation = last_signed_message.verification_explanation
transaction_sender_address = last_signed_message.sender_address
transaction_recipient_address = last_signed_message.recipient_address
transaction_hash = last_signed_message.transaction_hash
Expand All @@ -256,6 +259,8 @@ def payment_control_render(self, request: HttpRequest, payment: OrderPayment):
token_contract_address = last_signed_message.token_contract_address
is_testnet = last_signed_message.is_testnet
else:
verification_failed_permanently = None
verification_explanation = None
transaction_sender_address = None
transaction_recipient_address = None
transaction_hash = None
Expand All @@ -273,6 +278,8 @@ def payment_control_render(self, request: HttpRequest, payment: OrderPayment):
ctx = {
"payment_info": payment.info_data,
"wallet_address": hex_wallet_address,
"verification_failed_permanently": verification_failed_permanently,
"verification_explanation": verification_explanation,
"transaction_sender_address": transaction_sender_address,
"transaction_recipient_address": transaction_recipient_address,
"transaction_hash": transaction_hash,
Expand Down
6 changes: 6 additions & 0 deletions pretix_eth/templates/pretix_eth/control.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

{% if payment_info %}
<dl class="dl-horizontal">
{% if verification_failed_permanently %}
<dt><strong>{% trans "Verification Failed Permanently" %}</strong></dt>
<dd></dd>
{% endif %}
<dt>{% trans "Verification note" %}</dt>
<dd>{{ verification_explanation }}</dd>
<dt>{% trans "Amount" %}</dt>
<dd>{{ payment_info.amount }}</dd>
<dt>{% trans "Event Currency" %}</dt>
Expand Down
14 changes: 7 additions & 7 deletions pretix_eth/verifier/verify_payment.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,19 @@ def ensure_grpc_initialized():
# verify_payment synchronously calls the remote 3cities grpc service to
# attempt to verify the passed
# threecities.v1.transfer_verification_pb2.TransferVerificationRequest.
# Returns True if and only if the payment was successfully verified.
# Returns
# threecities.v1.transfer_verification_pb2.TransferVerificationResponse.
# Verification was successful if and only if response.is_verified.
def verify_payment(req):
is_verified = False
resp = None
ensure_grpc_initialized()
if not grpc_stub:
logger.error("grpc stub unavailable, payment verification cannot proceed")
else:
try:
resp = grpc_stub.TransferVerification(req)
logger.info(f"{resp.description} {resp.error}")
if resp.is_verified:
is_verified = True
logger.info(f"{resp.external_id} {resp.description} {resp.error}")
except grpc.RpcError as e:
logger.error(f"grpc call failed: {e}")
logger.error(f"grpc call failed. ${req.external_id} {e}")

return is_verified
return resp
22 changes: 11 additions & 11 deletions threecities/v1/transfer_verification_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 124e1ba

Please sign in to comment.