Skip to content

Commit 4d00988

Browse files
committed
Added descending order support to streams
1 parent 373b62f commit 4d00988

File tree

8 files changed

+254
-57
lines changed

8 files changed

+254
-57
lines changed

demo/desc_order_calls.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from transpose.contract import TransposeDecodedContract
2+
3+
4+
def descending_calls_demo(api_key: str) -> None:
5+
"""
6+
This demo shows how to stream all calls to a contract in descending
7+
order by block number.
8+
9+
:param api_key: Your Transpose API key.
10+
"""
11+
12+
# initialize WETH contract
13+
contract = TransposeDecodedContract(
14+
contract_address='0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
15+
abi_path='abi/weth-abi.json',
16+
chain='ethereum',
17+
api_key=api_key
18+
)
19+
20+
# build call stream
21+
stream = contract.stream_calls(
22+
start_block=4753925,
23+
order='desc'
24+
)
25+
26+
# iterate over stream
27+
for call in stream:
28+
print('{} ({}, {}, {})'.format(
29+
call['item']['function_name'],
30+
call['context']['block_number'],
31+
call['context']['transaction_position'],
32+
call['context']['trace_index']
33+
))

demo/desc_order_events.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from transpose.contract import TransposeDecodedContract
2+
3+
4+
def descending_events_demo(api_key: str) -> None:
5+
"""
6+
This demo shows how to stream all events to a contract in descending
7+
order by block number.
8+
9+
:param api_key: Your Transpose API key.
10+
"""
11+
12+
# initialize WETH contract
13+
contract = TransposeDecodedContract(
14+
contract_address='0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
15+
abi_path='abi/weth-abi.json',
16+
chain='ethereum',
17+
api_key=api_key
18+
)
19+
20+
# build call stream
21+
stream = contract.stream_events(
22+
start_block=4753925,
23+
order='desc'
24+
)
25+
26+
# iterate over stream
27+
for event in stream:
28+
print('{} ({}, {})'.format(
29+
event['item']['event_name'],
30+
event['context']['block_number'],
31+
event['context']['log_index']
32+
))

transpose/contract.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ def stream_events(self,
7676
event_name: str=None,
7777
start_block: int=None,
7878
end_block: int=None,
79+
order: str='asc',
7980
live_stream: bool=False,
8081
live_refresh_interval: int=3) -> Stream:
8182

@@ -85,20 +86,32 @@ def stream_events(self,
8586
:param event_name: The name of the event.
8687
:param start_block: The block to start streaming from, inclusive.
8788
:param end_block: The block to stop streaming at, exclusive.
89+
:param order: The order to stream the events in.
8890
:param live_stream: Whether to stream live data.
8991
:param live_refresh_interval: The interval for refreshing the data in seconds when live.
9092
:return: A Stream object.
9193
"""
9294

95+
# check order
96+
if order not in ['asc', 'desc']:
97+
raise ContractError('Invalid order (must be one of "asc" or "desc")')
98+
elif order == 'desc' and live_stream:
99+
raise ContractError('Cannot stream in descending order when live')
100+
93101
# set start and stop blocks
94102
next_block = self.__get_latest_block() + 1
95103
if live_stream:
96104
start_block = min(start_block, next_block) if start_block is not None else next_block
97105
end_block = None
98106
else:
99-
start_block = max(start_block, 0) if start_block is not None else 0
100-
end_block = min(end_block, next_block) if end_block is not None else next_block
101-
if start_block > end_block: raise ContractError('Invalid start and end blocks')
107+
if order == 'asc':
108+
start_block = max(start_block, 0) if start_block is not None else 0
109+
end_block = min(end_block, next_block) if end_block is not None else next_block
110+
if start_block > end_block: raise ContractError('Invalid start and end blocks')
111+
else:
112+
start_block = min(start_block, next_block) if start_block is not None else next_block
113+
end_block = max(end_block, 0) if end_block is not None else 0
114+
if start_block < end_block: raise ContractError('Invalid start and end blocks')
102115

103116
# return stream
104117
return EventStream(
@@ -109,6 +122,7 @@ def stream_events(self,
109122
event_name=event_name,
110123
start_block=start_block,
111124
end_block=end_block,
125+
order=order,
112126
live_stream=live_stream,
113127
live_refresh_interval=live_refresh_interval
114128
)
@@ -118,6 +132,7 @@ def stream_calls(self,
118132
function_name: str=None,
119133
start_block: int=None,
120134
end_block: int=None,
135+
order: str='asc',
121136
live_stream: bool=False,
122137
live_refresh_interval: int=3) -> Stream:
123138

@@ -127,20 +142,32 @@ def stream_calls(self,
127142
:param function_name: The name of the function.
128143
:param start_block: The block to start streaming from, inclusive.
129144
:param end_block: The block to stop streaming at, exclusive.
145+
:param order: The order to stream the calls in.
130146
:param live_stream: Whether to stream live data.
131147
:param live_refresh_interval: The interval for refreshing the data in seconds when live.
132148
:return: A Stream object.
133149
"""
134150

151+
# check order
152+
if order not in ['asc', 'desc']:
153+
raise ContractError('Invalid order (must be one of "asc" or "desc")')
154+
elif order == 'desc' and live_stream:
155+
raise ContractError('Cannot stream in descending order when live')
156+
135157
# set start and stop blocks
136158
next_block = self.__get_latest_block() + 1
137159
if live_stream:
138160
start_block = min(start_block, next_block) if start_block is not None else next_block
139161
end_block = None
140162
else:
141-
start_block = max(start_block, 0) if start_block is not None else 0
142-
end_block = min(end_block, next_block) if end_block is not None else next_block
143-
if start_block > end_block: raise ContractError('Invalid start and end blocks')
163+
if order == 'asc':
164+
start_block = max(start_block, 0) if start_block is not None else 0
165+
end_block = min(end_block, next_block) if end_block is not None else next_block
166+
if start_block > end_block: raise ContractError('Invalid start and end blocks')
167+
else:
168+
start_block = min(start_block, next_block) if start_block is not None else next_block
169+
end_block = max(end_block, 0) if end_block is not None else 0
170+
if start_block < end_block: raise ContractError('Invalid start and end blocks')
144171

145172
# return stream
146173
return CallStream(
@@ -151,6 +178,7 @@ def stream_calls(self,
151178
function_name=function_name,
152179
start_block=start_block,
153180
end_block=end_block,
181+
order=order,
154182
live_stream=live_stream,
155183
live_refresh_interval=live_refresh_interval
156184
)

transpose/sql/calls.py

Lines changed: 75 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
def calls_query(chain: str, contract_address: str, from_block: int, from_transaction_position: int, from_trace_index: int,
22
function_selector: str=None,
33
stop_block: int=None,
4+
order: str='asc',
45
limit: int=None) -> str:
56

67
"""
@@ -13,40 +14,81 @@ def calls_query(chain: str, contract_address: str, from_block: int, from_transac
1314
:param from_trace_index: The starting trace index, inclusive.
1415
:param function_selector: The function selector.
1516
:param stop_block: The ending block number, exclusive.
17+
:param order: The order to return the transactions and traces in.
1618
:param limit: The maximum number of transactions and traces to return.
1719
:return: The SQL query.
1820
"""
1921

20-
return \
21-
f"""
22-
SELECT * FROM (
23-
24-
(SELECT
25-
timestamp, block_number, transaction_hash, position AS transaction_position,
26-
0 AS trace_index, array[]::integer[] AS trace_address, 'call' AS trace_type,
27-
from_address, value, input, output, __confirmed
28-
FROM {chain}.transactions
29-
WHERE to_address = '{contract_address}'
30-
{f"AND LEFT(input, 10) = '{function_selector}'" if function_selector is not None else ""}
31-
AND (block_number, position) >= ({from_block}, {from_transaction_position})
32-
{f"AND block_number < {stop_block}" if stop_block is not None else ""}
33-
ORDER BY block_number ASC, transaction_position ASC)
34-
35-
UNION ALL
36-
37-
(SELECT
38-
timestamp, block_number, transaction_hash, transaction_position,
39-
trace_index + 1, trace_address, trace_type,
40-
from_address, value, input, output, __confirmed
41-
FROM {chain}.traces
42-
WHERE to_address = '{contract_address}'
43-
{f"AND LEFT(input, 10) = '{function_selector}'" if function_selector is not None else ""}
44-
AND (block_number, transaction_position, trace_index) >= ({from_block}, {from_transaction_position}, {from_trace_index})
45-
{f"AND block_number < {stop_block}" if stop_block is not None else ""}
46-
ORDER BY block_number ASC, transaction_position ASC)
47-
48-
) AS t
49-
50-
ORDER BY block_number ASC, transaction_position ASC, trace_index ASC
51-
{f"LIMIT {limit}" if limit is not None else ""}
52-
"""
22+
if order == 'asc':
23+
return \
24+
f"""
25+
SELECT * FROM (
26+
27+
(SELECT
28+
timestamp, block_number, transaction_hash, position AS transaction_position,
29+
0 AS trace_index, array[]::integer[] AS trace_address, 'call' AS trace_type,
30+
from_address, value, input, output, __confirmed
31+
FROM {chain}.transactions
32+
WHERE to_address = '{contract_address}'
33+
{f"AND LEFT(input, 10) = '{function_selector}'" if function_selector is not None else ""}
34+
AND (block_number, position) >= ({from_block}, {from_transaction_position})
35+
{f"AND block_number < {stop_block}" if stop_block is not None else ""}
36+
ORDER BY block_number ASC, transaction_position ASC
37+
{f"LIMIT {limit}" if limit is not None else ""})
38+
39+
UNION ALL
40+
41+
(SELECT
42+
timestamp, block_number, transaction_hash, transaction_position,
43+
trace_index + 1, trace_address, trace_type,
44+
from_address, value, input, output, __confirmed
45+
FROM {chain}.traces
46+
WHERE to_address = '{contract_address}'
47+
{f"AND LEFT(input, 10) = '{function_selector}'" if function_selector is not None else ""}
48+
AND (block_number, transaction_position, trace_index) >= ({from_block}, {from_transaction_position}, {from_trace_index})
49+
{f"AND block_number < {stop_block}" if stop_block is not None else ""}
50+
ORDER BY block_number ASC, transaction_position ASC, trace_index ASC
51+
{f"LIMIT {limit}" if limit is not None else ""})
52+
53+
) AS t
54+
55+
ORDER BY block_number ASC, transaction_position ASC, trace_index ASC
56+
{f"LIMIT {limit}" if limit is not None else ""}
57+
"""
58+
59+
else:
60+
return \
61+
f"""
62+
SELECT * FROM (
63+
64+
(SELECT
65+
timestamp, block_number, transaction_hash, position AS transaction_position,
66+
0 AS trace_index, array[]::integer[] AS trace_address, 'call' AS trace_type,
67+
from_address, value, input, output, __confirmed
68+
FROM {chain}.transactions
69+
WHERE to_address = '{contract_address}'
70+
{f"AND LEFT(input, 10) = '{function_selector}'" if function_selector is not None else ""}
71+
AND (block_number, position) <= ({from_block}, {from_transaction_position})
72+
{f"AND block_number > {stop_block}" if stop_block is not None else ""}
73+
ORDER BY block_number DESC, transaction_position DESC
74+
{f"LIMIT {limit}" if limit is not None else ""})
75+
76+
UNION ALL
77+
78+
(SELECT
79+
timestamp, block_number, transaction_hash, transaction_position,
80+
trace_index + 1, trace_address, trace_type,
81+
from_address, value, input, output, __confirmed
82+
FROM {chain}.traces
83+
WHERE to_address = '{contract_address}'
84+
{f"AND LEFT(input, 10) = '{function_selector}'" if function_selector is not None else ""}
85+
AND (block_number, transaction_position, trace_index) <= ({from_block}, {from_transaction_position}, {from_trace_index})
86+
{f"AND block_number > {stop_block}" if stop_block is not None else ""}
87+
ORDER BY block_number DESC, transaction_position DESC, trace_index DESC
88+
{f"LIMIT {limit}" if limit is not None else ""})
89+
90+
) AS t
91+
92+
ORDER BY block_number DESC, transaction_position DESC, trace_index DESC
93+
{f"LIMIT {limit}" if limit is not None else ""}
94+
"""

transpose/sql/events.py

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
def events_query(chain: str, contract_address: str, from_block: int, from_log_index: int,
22
topic_0: str=None,
33
stop_block: int=None,
4+
order: str='asc',
45
limit: int=None) -> str:
56

67
"""
@@ -12,18 +13,33 @@ def events_query(chain: str, contract_address: str, from_block: int, from_log_in
1213
:param from_log_index: The starting log index, inclusive.
1314
:param topic_0: The event signature.
1415
:param stop_block: The ending block number, exclusive.
16+
:param order: The order to return the logs in.
1517
:param limit: The maximum number of logs to return.
1618
:return: The SQL query.
1719
"""
1820

19-
return \
20-
f"""
21-
SELECT timestamp, block_number, log_index, transaction_hash, transaction_position, address, data, topic_0, topic_1, topic_2, topic_3, __confirmed
22-
FROM {chain}.logs
23-
WHERE address = '{contract_address}'
24-
{f"AND topic_0 = '{topic_0}'" if topic_0 is not None else ""}
25-
AND (block_number, log_index) >= ({from_block}, {from_log_index})
26-
{f"AND block_number < {stop_block}" if stop_block is not None else ""}
27-
ORDER BY block_number ASC, log_index ASC
28-
{f"LIMIT {limit}" if limit is not None else ""}
29-
"""
21+
if order == 'asc':
22+
return \
23+
f"""
24+
SELECT timestamp, block_number, log_index, transaction_hash, transaction_position, address, data, topic_0, topic_1, topic_2, topic_3, __confirmed
25+
FROM {chain}.logs
26+
WHERE address = '{contract_address}'
27+
{f"AND topic_0 = '{topic_0}'" if topic_0 is not None else ""}
28+
AND (block_number, log_index) >= ({from_block}, {from_log_index})
29+
{f"AND block_number < {stop_block}" if stop_block is not None else ""}
30+
ORDER BY block_number ASC, log_index ASC
31+
{f"LIMIT {limit}" if limit is not None else ""}
32+
"""
33+
34+
else:
35+
return \
36+
f"""
37+
SELECT timestamp, block_number, log_index, transaction_hash, transaction_position, address, data, topic_0, topic_1, topic_2, topic_3, __confirmed
38+
FROM {chain}.logs
39+
WHERE address = '{contract_address}'
40+
{f"AND topic_0 = '{topic_0}'" if topic_0 is not None else ""}
41+
AND (block_number, log_index) <= ({from_block}, {from_log_index})
42+
{f"AND block_number > {stop_block}" if stop_block is not None else ""}
43+
ORDER BY block_number DESC, log_index DESC
44+
{f"LIMIT {limit}" if limit is not None else ""}
45+
"""

0 commit comments

Comments
 (0)