Skip to content

Commit d9297e2

Browse files
authored
feat: consensus deduct processing time appeal (#867)
* feat: add rollup transaction db table * fix: drop audit table, change id and nonce of rollup, only make one finalized rollup transaction * create rollup transaction for every validator * add function to mock in consensus test * feat: add appeal window, accepted queue, basic test * fix: old tests were stuck on accepted state, add usage of thread * refactor consensus into states * feat: added appeal flow when transaction is accepted including the loop when appeal failed and succeeded * fix: adding tests for appeals and fixing minor bugs * refactor: merge main and PR #573 into this branch * feat: add appeal_failed in db, select new validators when appealed based on formula * docs: cleanup consensus mechanism base file * test: checking the number of validators for different appeals * feat: adding appeal window to undetermined state * feat: change timestamp_accepted and add appeal_undetermined in database * feat: undetermined to pending, activate frontend button in undetermined state and add button also to modal * feat: leader only has no appeal button and no appeal window * feat: implement the state transitions to process the appeal - Add N+2 validators, remove leader - Use latest data of transaction when in pending state and not the old one from the crawler - Write consensus data before setting status to have it updated in frontend when going to transaction info modal - Do not deploy contract when transaction was in the undetermined state * test: add test for leader appeals * refactor: merge 593-appeals-add-validators-when-appealed into 604-appeals-implement-sequential-appeals-fail * refactor: merging changed file permissions * fix: appealing a write method gave a KeyError because of wrong conversation of transaction * docs: update transaction_processor argument description * fix: all appeals disagreed because of pending_transactions type * refactor: undo change because of merge * fix: do not reset finality window when leader appeal failed, leader_only check in appeal_window * fix: set value in database migration file * fix: add leader_only check for modal appeal button, comment out modal appeal button * fix: do not show appeal button when finality window is finished, checking on finalized state gave a small delay in showing when appeal failed and finality window was exceeded * fix: we do not emit messages when the transaction goes from undetermined to finalized * fix: complete merge * fix: redirect leader appeal is processed by appeal queue, not pending queue * fix: test increase wait time to get to finalized * fix: use appeal property of the transaction in frontend * fix: comment out appeal buttons * refactor: clean up prints * fix: check status typo * fix: update consensus test * fix: update test_get_highest_timestamp * feat: deduct processing time appeal * fix: solve consensus test timings * fix: solve consensus test timings * fix: changes check timings consensus test * fix: add activated to status match check in consensus test * fix: split set_transaction_appeal_processing_time * fix: add log_terminal argument in messagehandler mock; remove messagehandler argument in set_transaction_appeal * fix: database migration file revision duplicates
1 parent 60f955c commit d9297e2

File tree

7 files changed

+224
-19
lines changed

7 files changed

+224
-19
lines changed

backend/consensus/base.py

+36-2
Original file line numberDiff line numberDiff line change
@@ -870,7 +870,11 @@ def can_finalize_transaction(
870870
bool: True if the transaction can be finalized, False otherwise.
871871
"""
872872
if (transaction.leader_only) or (
873-
(int(time.time()) - transaction.timestamp_awaiting_finalization)
873+
(
874+
int(time.time())
875+
- transaction.timestamp_awaiting_finalization
876+
- transaction.appeal_processing_time
877+
)
874878
> self.finality_window_time
875879
):
876880
if index == 0:
@@ -1092,7 +1096,7 @@ async def process_validator_appeal(
10921096
)
10931097
)
10941098
context.transactions_processor.set_transaction_appeal(
1095-
context.transaction.hash, False, self.msg_handler
1099+
context.transaction.hash, False
10961100
)
10971101
context.transaction.appealed = False
10981102
self.msg_handler.send_message(
@@ -1107,6 +1111,9 @@ async def process_validator_appeal(
11071111
),
11081112
log_to_terminal=False,
11091113
)
1114+
context.transactions_processor.set_transaction_appeal_processing_time(
1115+
context.transaction.hash
1116+
)
11101117
else:
11111118
# Set up the context for the committing state
11121119
context.num_validators = len(context.remaining_validators)
@@ -1697,6 +1704,15 @@ async def handle(self, context):
16971704
None,
16981705
context.validation_results,
16991706
)
1707+
1708+
# Reset the appeal processing time
1709+
context.transactions_processor.reset_transaction_appeal_processing_time(
1710+
context.transaction.hash
1711+
)
1712+
context.transactions_processor.set_transaction_timestamp_appeal(
1713+
context.transaction.hash, None
1714+
)
1715+
17001716
return "validator_appeal_success"
17011717

17021718
else:
@@ -1851,8 +1867,20 @@ async def handle(self, context):
18511867
context.transaction.hash, False
18521868
)
18531869
context.transaction.appeal_undetermined = False
1870+
context.transactions_processor.reset_transaction_appeal_processing_time(
1871+
context.transaction.hash
1872+
)
1873+
context.transactions_processor.set_transaction_timestamp_appeal(
1874+
context.transaction.hash, None
1875+
)
1876+
context.transaction.timestamp_appeal = None
18541877
return "leader_appeal_success"
18551878
else:
1879+
# Increment the appeal processing time when the transaction was appealed
1880+
if context.transaction.timestamp_appeal is not None:
1881+
context.transactions_processor.set_transaction_appeal_processing_time(
1882+
context.transaction.hash
1883+
)
18561884
return None
18571885

18581886

@@ -1927,6 +1955,12 @@ async def handle(self, context):
19271955
context.transaction.hash
19281956
)
19291957

1958+
# Increment the appeal processing time when the transaction was appealed
1959+
if context.transaction.timestamp_appeal is not None:
1960+
context.transactions_processor.set_transaction_appeal_processing_time(
1961+
context.transaction.hash
1962+
)
1963+
19301964
return None
19311965

19321966

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""appeal_processing_time
2+
3+
Revision ID: d932a5fef8b1
4+
Revises: 2de9c3151194
5+
Create Date: 2025-01-31 15:39:44.618075
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
from alembic import op
12+
import sqlalchemy as sa
13+
14+
15+
# revision identifiers, used by Alembic.
16+
revision: str = "d932a5fef8b1"
17+
down_revision: Union[str, None] = "2de9c3151194"
18+
branch_labels: Union[str, Sequence[str], None] = None
19+
depends_on: Union[str, Sequence[str], None] = None
20+
21+
22+
def upgrade() -> None:
23+
# ### commands auto generated by Alembic - please adjust! ###
24+
op.add_column(
25+
"transactions", sa.Column("timestamp_appeal", sa.BigInteger(), nullable=True)
26+
)
27+
op.add_column(
28+
"transactions", sa.Column("appeal_processing_time", sa.Integer(), nullable=True)
29+
)
30+
# ### end Alembic commands ###
31+
32+
33+
def downgrade() -> None:
34+
# ### commands auto generated by Alembic - please adjust! ###
35+
op.drop_column("transactions", "appeal_processing_time")
36+
op.drop_column("transactions", "timestamp_appeal")
37+
# ### end Alembic commands ###

backend/database_handler/models.py

+2
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ class Transactions(Base):
9898
ghost_contract_address: Mapped[Optional[str]] = mapped_column(String(255))
9999
appeal_failed: Mapped[Optional[int]] = mapped_column(Integer)
100100
consensus_history: Mapped[Optional[dict]] = mapped_column(JSONB)
101+
timestamp_appeal: Mapped[Optional[int]] = mapped_column(BigInteger)
102+
appeal_processing_time: Mapped[Optional[int]] = mapped_column(Integer)
101103

102104
# Relationship for triggered transactions
103105
triggered_by_hash: Mapped[Optional[str]] = mapped_column(

backend/database_handler/transactions_processor.py

+37-4
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ def _parse_transaction_data(transaction_data: Transactions) -> dict:
6868
"appeal_failed": transaction_data.appeal_failed,
6969
"appeal_undetermined": transaction_data.appeal_undetermined,
7070
"consensus_history": transaction_data.consensus_history,
71+
"timestamp_appeal": transaction_data.timestamp_appeal,
72+
"appeal_processing_time": transaction_data.appeal_processing_time,
7173
}
7274

7375
@staticmethod
@@ -178,6 +180,8 @@ def insert_transaction(
178180
appeal_failed=0,
179181
appeal_undetermined=False,
180182
consensus_history={},
183+
timestamp_appeal=None,
184+
appeal_processing_time=0,
181185
)
182186

183187
self.session.add(new_transaction)
@@ -312,12 +316,15 @@ def set_transaction_appeal(self, transaction_hash: str, appeal: bool):
312316
)
313317
# You can only appeal the transaction if it is in accepted or undetermined state
314318
# Setting it to false is always allowed
315-
if (
316-
(not appeal)
317-
or (transaction.status == TransactionStatus.ACCEPTED)
318-
or (transaction.status == TransactionStatus.UNDETERMINED)
319+
if not appeal:
320+
transaction.appealed = appeal
321+
self.session.commit()
322+
elif transaction.status in (
323+
TransactionStatus.ACCEPTED,
324+
TransactionStatus.UNDETERMINED,
319325
):
320326
transaction.appealed = appeal
327+
self.set_transaction_timestamp_appeal(transaction, int(time.time()))
321328
self.session.commit()
322329

323330
def set_transaction_timestamp_awaiting_finalization(
@@ -450,3 +457,29 @@ def update_consensus_history(
450457

451458
flag_modified(transaction, "consensus_history")
452459
self.session.commit()
460+
461+
def set_transaction_timestamp_appeal(
462+
self, transaction: Transactions | str, timestamp_appeal: int
463+
):
464+
if isinstance(transaction, str): # hash
465+
transaction = (
466+
self.session.query(Transactions).filter_by(hash=transaction).one()
467+
)
468+
transaction.timestamp_appeal = timestamp_appeal
469+
470+
def set_transaction_appeal_processing_time(self, transaction_hash: str):
471+
transaction = (
472+
self.session.query(Transactions).filter_by(hash=transaction_hash).one()
473+
)
474+
transaction.appeal_processing_time += (
475+
round(time.time()) - transaction.timestamp_appeal
476+
)
477+
flag_modified(transaction, "appeal_processing_time")
478+
self.session.commit()
479+
480+
def reset_transaction_appeal_processing_time(self, transaction_hash: str):
481+
transaction = (
482+
self.session.query(Transactions).filter_by(hash=transaction_hash).one()
483+
)
484+
transaction.appeal_processing_time = 0
485+
self.session.commit()

backend/domain/types.py

+6
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ class Transaction:
8787
appeal_failed: int = 0
8888
appeal_undetermined: bool = False
8989
consensus_history: dict = field(default_factory=dict)
90+
timestamp_appeal: int | None = None
91+
appeal_processing_time: int = 0
9092

9193
def to_dict(self):
9294
return {
@@ -114,6 +116,8 @@ def to_dict(self):
114116
"appeal_failed": self.appeal_failed,
115117
"appeal_undetermined": self.appeal_undetermined,
116118
"consensus_history": self.consensus_history,
119+
"timestamp_appeal": self.timestamp_appeal,
120+
"appeal_processing_time": self.appeal_processing_time,
117121
}
118122

119123
@classmethod
@@ -143,4 +147,6 @@ def from_dict(cls, input: dict) -> "Transaction":
143147
appeal_failed=input.get("appeal_failed", 0),
144148
appeal_undetermined=input.get("appeal_undetermined", False),
145149
consensus_history=input.get("consensus_history"),
150+
timestamp_appeal=input.get("timestamp_appeal"),
151+
appeal_processing_time=input.get("appeal_processing_time", 0),
146152
)

tests/unit/consensus/test_base.py

+77
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,13 @@ def get_vote():
244244
]
245245
== timestamp_awaiting_finalization_1
246246
)
247+
248+
assert (
249+
transactions_processor.get_transaction_by_hash(transaction.hash)[
250+
"appeal_processing_time"
251+
]
252+
> 0
253+
)
247254
finally:
248255
cleanup_threads(event, threads)
249256

@@ -309,6 +316,12 @@ def get_vote():
309316
]
310317
== timestamp_awaiting_finalization_1
311318
)
319+
assert (
320+
transactions_processor.get_transaction_by_hash(transaction.hash)[
321+
"appeal_processing_time"
322+
]
323+
> 0
324+
)
312325
finally:
313326
cleanup_threads(event, threads)
314327

@@ -434,6 +447,13 @@ def get_vote():
434447

435448
assert new_leader_address != old_leader_address
436449
assert new_leader_address in validator_set_addresses
450+
451+
assert (
452+
transactions_processor.get_transaction_by_hash(transaction.hash)[
453+
"appeal_processing_time"
454+
]
455+
== 0
456+
)
437457
finally:
438458
cleanup_threads(event, threads)
439459

@@ -807,6 +827,12 @@ def get_vote():
807827
)
808828
leader_address = get_leader_address(transaction, transactions_processor)
809829

830+
appeal_processing_time_temp = transactions_processor.get_transaction_by_hash(
831+
transaction.hash
832+
)["appeal_processing_time"]
833+
assert appeal_processing_time_temp == 0
834+
timestamp_appeal_temp = 0
835+
810836
for appeal_failed in range(3):
811837
assert (
812838
transactions_processor.get_transaction_by_hash(transaction.hash)[
@@ -821,6 +847,18 @@ def get_vote():
821847
transactions_processor, transaction, [TransactionStatus.ACCEPTED.value]
822848
)
823849

850+
appeal_processing_time_new = transactions_processor.get_transaction_by_hash(
851+
transaction.hash
852+
)["appeal_processing_time"]
853+
assert appeal_processing_time_new > appeal_processing_time_temp
854+
appeal_processing_time_temp = appeal_processing_time_new
855+
856+
timestamp_appeal_new = transactions_processor.get_transaction_by_hash(
857+
transaction.hash
858+
)["timestamp_appeal"]
859+
assert timestamp_appeal_new > timestamp_appeal_temp
860+
timestamp_appeal_temp = timestamp_appeal_new
861+
824862
assert (
825863
transactions_processor.get_transaction_by_hash(transaction.hash)[
826864
"timestamp_awaiting_finalization"
@@ -1229,6 +1267,19 @@ def get_vote():
12291267
check_validator_count(transaction, transactions_processor, nb_validators)
12301268
assert len(created_nodes) == nb_created_nodes
12311269

1270+
assert (
1271+
transactions_processor.get_transaction_by_hash(transaction.hash)[
1272+
"appeal_processing_time"
1273+
]
1274+
> 0
1275+
)
1276+
assert (
1277+
transactions_processor.get_transaction_by_hash(transaction.hash)[
1278+
"timestamp_appeal"
1279+
]
1280+
is not None
1281+
)
1282+
12321283
appeal(transaction, transactions_processor)
12331284
assert_transaction_status_match(
12341285
transactions_processor, transaction, [TransactionStatus.ACCEPTED.value]
@@ -1252,6 +1303,19 @@ def get_vote():
12521303
check_validator_count(transaction, transactions_processor, nb_validators)
12531304
assert len(created_nodes) == nb_created_nodes
12541305

1306+
assert (
1307+
transactions_processor.get_transaction_by_hash(transaction.hash)[
1308+
"appeal_processing_time"
1309+
]
1310+
== 0
1311+
)
1312+
assert (
1313+
transactions_processor.get_transaction_by_hash(transaction.hash)[
1314+
"timestamp_appeal"
1315+
]
1316+
is None
1317+
)
1318+
12551319
appeal(transaction, transactions_processor)
12561320
current_status = assert_transaction_status_match(
12571321
transactions_processor,
@@ -1323,5 +1387,18 @@ def get_vote():
13231387
nb_created_nodes += nb_validators**2
13241388
check_validator_count(transaction, transactions_processor, nb_validators)
13251389
assert len(created_nodes) == nb_created_nodes
1390+
1391+
assert (
1392+
transactions_processor.get_transaction_by_hash(transaction.hash)[
1393+
"appeal_processing_time"
1394+
]
1395+
> 0
1396+
)
1397+
assert (
1398+
transactions_processor.get_transaction_by_hash(transaction.hash)[
1399+
"timestamp_appeal"
1400+
]
1401+
is not None
1402+
)
13261403
finally:
13271404
cleanup_threads(event, threads)

0 commit comments

Comments
 (0)