Skip to content

Commit 771db2a

Browse files
denishacquinDencristiam86
authored
feat: update transactions based on subscriptions events and remove polling (#583)
* fix: only show deployed contract notifications when address changes * transaction subscription and refresh working * rename plugin * cleanup * move plugin to a hook, rename back to listener, fix notification logic and improve deployment detection * cleanup logs and todos * cleanup * move notify back, update unit tests * implement tx subscription system * cleanup, fix tests * add unit test for listener hook * remove pending computed test, add unit test for tx refresh * exclude frontend test folder from sonarcloud analysis * remove client_session_id from transactions --------- Co-authored-by: Den <den@Deniss-MacBook-Pro.local> Co-authored-by: Cristiam Da Silva <cristiam86@gmail.com>
1 parent cb516e4 commit 771db2a

22 files changed

+310
-134
lines changed

backend/consensus/base.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,7 @@ async def exec_transaction(
151151
Node,
152152
] = node_factory,
153153
):
154-
msg_handler = self.msg_handler.with_client_session(
155-
transaction.client_session_id
156-
)
154+
msg_handler = self.msg_handler
157155
if (
158156
transactions_processor.get_transaction_by_hash(transaction.hash)["status"]
159157
!= TransactionStatus.PENDING.value
@@ -361,7 +359,6 @@ async def exec_transaction(
361359
type=TransactionType.RUN_CONTRACT.value,
362360
nonce=nonce,
363361
leader_only=transaction.leader_only, # Cascade
364-
client_session_id=transaction.client_session_id,
365362
triggered_by_hash=transaction.hash,
366363
)
367364

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""remove client session id from transactions
2+
3+
Revision ID: 579e86111b36
4+
Revises: ab256b41602a
5+
Create Date: 2024-11-08 10:41:56.112444
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 = "579e86111b36"
17+
down_revision: Union[str, None] = "ab256b41602a"
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.drop_column("transactions", "client_session_id")
25+
# ### end Alembic commands ###
26+
27+
28+
def downgrade() -> None:
29+
# ### commands auto generated by Alembic - please adjust! ###
30+
op.add_column(
31+
"transactions",
32+
sa.Column(
33+
"client_session_id",
34+
sa.VARCHAR(length=255),
35+
autoincrement=False,
36+
nullable=True,
37+
),
38+
)
39+
# ### end Alembic commands ###

backend/database_handler/models.py

-3
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,6 @@ class Transactions(Base):
8989
created_at: Mapped[Optional[datetime.datetime]] = mapped_column(
9090
DateTime(True), server_default=func.current_timestamp(), init=False
9191
)
92-
client_session_id: Mapped[Optional[str]] = mapped_column(
93-
String(255)
94-
) # Used to identify the client session that is subscribed to this transaction's events
9592
leader_only: Mapped[bool] = mapped_column(Boolean)
9693
r: Mapped[Optional[int]] = mapped_column(Integer)
9794
s: Mapped[Optional[int]] = mapped_column(Integer)

backend/database_handler/transactions_processor.py

-3
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ def _parse_transaction_data(transaction_data: Transactions) -> dict:
4343
"v": transaction_data.v,
4444
"created_at": transaction_data.created_at.isoformat(),
4545
"leader_only": transaction_data.leader_only,
46-
"client_session_id": transaction_data.client_session_id,
4746
"triggered_by": transaction_data.triggered_by_hash,
4847
"triggered_transactions": [
4948
transaction.hash
@@ -113,7 +112,6 @@ def insert_transaction(
113112
type: int,
114113
nonce: int,
115114
leader_only: bool,
116-
client_session_id: str | None,
117115
triggered_by_hash: (
118116
str | None
119117
) = None, # If filled, the transaction must be present in the database (committed)
@@ -146,7 +144,6 @@ def insert_transaction(
146144
s=None,
147145
v=None,
148146
leader_only=leader_only,
149-
client_session_id=client_session_id,
150147
triggered_by=(
151148
self.session.query(Transactions).filter_by(hash=triggered_by_hash).one()
152149
if triggered_by_hash

backend/domain/types.py

-3
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ class Transaction:
7979
leader_only: bool = (
8080
False # Flag to indicate if this transaction should be processed only by the leader. Used for fast and cheap execution of transactions.
8181
)
82-
client_session_id: str | None = None
8382

8483
def to_dict(self):
8584
return {
@@ -98,7 +97,6 @@ def to_dict(self):
9897
"s": self.s,
9998
"v": self.v,
10099
"leader_only": self.leader_only,
101-
"client_session_id": self.client_session_id,
102100
}
103101

104102

@@ -119,5 +117,4 @@ def transaction_from_dict(input: dict) -> Transaction:
119117
s=input.get("s"),
120118
v=input.get("v"),
121119
leader_only=input.get("leader_only", False),
122-
client_session_id=input["client_session_id"],
123120
)

backend/protocol_rpc/endpoints.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def fund_account(
7272

7373
nonce = transactions_processor.get_transaction_count(None)
7474
transaction_hash = transactions_processor.insert_transaction(
75-
None, account_address, None, amount, 0, nonce, False, get_client_session_id()
75+
None, account_address, None, amount, 0, nonce, False
7676
)
7777
return transaction_hash
7878

@@ -485,7 +485,6 @@ def send_raw_transaction(
485485
transaction_type,
486486
nonce,
487487
leader_only,
488-
get_client_session_id(),
489488
)
490489

491490
return transaction_hash

backend/protocol_rpc/message_handler/base.py

+14-7
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,20 @@ def log_endpoint_info(self, func):
4141
return log_endpoint_info_wrapper(self, self.config)(func)
4242

4343
def _socket_emit(self, log_event: LogEvent):
44-
self.socketio.emit(
45-
log_event.name,
46-
log_event.to_dict(),
47-
to=log_event.client_session_id
48-
or self.client_session_id
49-
or get_client_session_id(),
50-
)
44+
if log_event.name == "transaction_status_updated":
45+
self.socketio.emit(
46+
log_event.name,
47+
log_event.to_dict(),
48+
room=log_event.data.get("hash"),
49+
)
50+
else:
51+
self.socketio.emit(
52+
log_event.name,
53+
log_event.to_dict(),
54+
to=log_event.client_session_id
55+
or self.client_session_id
56+
or get_client_session_id(),
57+
)
5158

5259
def _log_message(self, log_event: LogEvent):
5360
logging_status = log_event.type.value

backend/protocol_rpc/server.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import logging
77
from flask import Flask
88
from flask_jsonrpc import JSONRPC
9-
from flask_socketio import SocketIO
9+
from flask_socketio import SocketIO, join_room, leave_room
1010
from flask_cors import CORS
1111
from flask_sqlalchemy import SQLAlchemy
1212
from sqlalchemy import create_engine
@@ -131,6 +131,17 @@ def run_socketio():
131131
host="0.0.0.0",
132132
allow_unsafe_werkzeug=True,
133133
)
134+
135+
@socketio.on("subscribe")
136+
def handle_subscribe(topics):
137+
for topic in topics:
138+
join_room(topic)
139+
140+
@socketio.on("unsubscribe")
141+
def handle_unsubscribe(topics):
142+
for topic in topics:
143+
leave_room(topic)
144+
134145
logging.getLogger("werkzeug").setLevel(
135146
os.environ.get("FLASK_LOG_LEVEL", logging.ERROR)
136147
)

frontend/src/components/Simulator/TransactionItem.vue

-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ const nodeStore = useNodeStore();
1616
const props = defineProps<{
1717
transaction: TransactionItem;
1818
}>();
19-
console.log('🚀 ~ transaction:', props.transaction);
2019
2120
const isDetailsModalOpen = ref(false);
2221

frontend/src/hooks/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export * from './useInputMap';
88
export * from './useContractQueries';
99
export * from './useFileName';
1010
export * from './useSetupStores';
11+
export * from './useTransactionListener';

frontend/src/hooks/useSetupStores.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
useNodeStore,
66
useTutorialStore,
77
} from '@/stores';
8-
import { useDb } from '@/hooks';
8+
import { useDb, useTransactionListener } from '@/hooks';
99
import { v4 as uuidv4 } from 'uuid';
1010
import type { Address } from '@/types';
1111

@@ -17,6 +17,7 @@ export const useSetupStores = () => {
1717
const nodeStore = useNodeStore();
1818
const tutorialStore = useTutorialStore();
1919
const db = useDb();
20+
const transactionListener = useTransactionListener();
2021
const contractFiles = await db.contractFiles.toArray();
2122
const exampleFiles = contractFiles.filter((c) => c.example);
2223

@@ -48,6 +49,9 @@ export const useSetupStores = () => {
4849
contractsStore.deployedContracts = await db.deployedContracts.toArray();
4950
transactionsStore.transactions = await db.transactions.toArray();
5051

52+
transactionsStore.initSubscriptions();
53+
transactionsStore.refreshPendingTransactions();
54+
transactionListener.init();
5155
contractsStore.getInitialOpenedFiles();
5256
tutorialStore.resetTutorialState();
5357
nodeStore.getValidatorsData();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { useTransactionsStore } from '@/stores';
2+
import type { TransactionItem } from '@/types';
3+
import { useWebSocketClient } from '@/hooks';
4+
5+
export function useTransactionListener() {
6+
const transactionsStore = useTransactionsStore();
7+
const webSocketClient = useWebSocketClient();
8+
9+
function init() {
10+
webSocketClient.on(
11+
'transaction_status_updated',
12+
handleTransactionStatusUpdate,
13+
);
14+
}
15+
16+
async function handleTransactionStatusUpdate(eventData: any) {
17+
const newTx = await transactionsStore.getTransaction(eventData.data.hash);
18+
19+
if (!newTx) {
20+
console.warn('Server tx not found for local tx:', newTx);
21+
transactionsStore.removeTransaction(newTx);
22+
return;
23+
}
24+
25+
const currentTx = transactionsStore.transactions.find(
26+
(t: TransactionItem) => t.hash === eventData.data.hash,
27+
);
28+
29+
if (!currentTx) {
30+
// This happens regularly when local transactions get cleared (e.g. user clears all txs or deploys new contract instance)
31+
return;
32+
}
33+
34+
transactionsStore.updateTransaction(newTx);
35+
}
36+
37+
return {
38+
init,
39+
};
40+
}

frontend/src/main.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { createPinia } from 'pinia';
44
import Notifications from '@kyvg/vue3-notification';
55
import App from './App.vue';
66
import router from './router';
7-
import { persistStorePlugin, TransactionsListenerPlugin } from '@/plugins';
7+
import { persistStorePlugin } from '@/plugins';
88
import { VueSpinnersPlugin } from 'vue3-spinners';
99
import registerGlobalComponents from '@/components/global/registerGlobalComponents';
1010
import { VueQueryPlugin } from '@tanstack/vue-query';
@@ -32,9 +32,6 @@ app.use(FloatingVue, {
3232
});
3333
app.use(Notifications);
3434
app.use(VueSpinnersPlugin);
35-
app.use(TransactionsListenerPlugin, {
36-
interval: 5000,
37-
});
3835

3936
const plausible = createPlausible({
4037
init: {

frontend/src/plugins/index.ts

-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
export * from './persistStore';
2-
export * from './transactionsListener';

frontend/src/plugins/transactionsListener.ts

-64
This file was deleted.

frontend/src/stores/contracts.ts

+1
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ export const useContractsStore = defineStore('contractsStore', () => {
111111
const index = deployedContracts.value.findIndex(
112112
(c) => c.contractId === contractId,
113113
);
114+
114115
const newItem = { contractId, address, defaultState };
115116

116117
if (index === -1) {

frontend/src/stores/node.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@ export const useNodeStore = defineStore('nodeStore', () => {
4242
];
4343

4444
trackedEvents.forEach((eventName) => {
45-
webSocketClient.on(eventName, (data: any) => {
45+
webSocketClient.on(eventName, (eventData: any) => {
4646
addLog({
47-
scope: data.scope,
48-
name: data.name,
49-
type: data.type,
50-
message: data.message,
51-
data: data.data,
47+
scope: eventData.scope,
48+
name: eventData.name,
49+
type: eventData.type,
50+
message: eventData.message,
51+
data: eventData.data,
5252
});
5353
});
5454
});

0 commit comments

Comments
 (0)