Skip to content

Commit 2c13a70

Browse files
committed
nonce management
1 parent 7bfe3f1 commit 2c13a70

6 files changed

Lines changed: 297 additions & 19 deletions

File tree

lighter/errors.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class ValidationError(ValueError):
2+
pass

lighter/nonce_manager.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import abc
2+
import enum
3+
from typing import Optional, Tuple
4+
5+
import requests
6+
7+
from lighter import api_client
8+
from lighter.api import transaction_api
9+
from lighter.errors import ValidationError
10+
11+
12+
def get_nonce_from_api(client: api_client.ApiClient, account_index: int, api_key_index: int) -> int:
13+
# uses request to avoid async initialization
14+
req = requests.get(
15+
client.configuration.host + "/api/v1/nextNonce",
16+
params={"account_index": account_index, "api_key_index": api_key_index},
17+
)
18+
if req.status_code != 200:
19+
raise Exception(f"couldn't get nonce {req.content}")
20+
return req.json()["nonce"]
21+
22+
23+
class NonceManager(abc.ABC):
24+
def __init__(
25+
self,
26+
account_index: int,
27+
api_client: api_client.ApiClient,
28+
start_api_key: int,
29+
end_api_key: Optional[int] = None,
30+
):
31+
if end_api_key is None:
32+
end_api_key = start_api_key
33+
if start_api_key > end_api_key or start_api_key >= 255 or end_api_key >= 255:
34+
raise ValidationError(f"invalid range {start_api_key=} {end_api_key=}")
35+
self.start_api_key = start_api_key
36+
self.end_api_key = end_api_key
37+
self.current_api_key = end_api_key # start will be used for the first tx
38+
self.account_index = account_index
39+
self.api_client = api_client
40+
self.nonce = {
41+
api_key_index: get_nonce_from_api(api_client, account_index, api_key_index) - 1
42+
for api_key_index in range(start_api_key, end_api_key + 1)
43+
}
44+
45+
def hard_refresh_nonce(self, api_key: int):
46+
self.nonce[api_key] = get_nonce_from_api(self.api_client, self.account_index, api_key) - 1
47+
48+
@abc.abstractmethod
49+
def next_nonce(self) -> Tuple[int, int]:
50+
pass
51+
52+
def acknowledge_failure(self, api_key_index: int) -> None:
53+
pass
54+
55+
56+
def increment_circular(idx: int, start_idx: int, end_idx: int) -> int:
57+
idx += 1
58+
if idx > end_idx:
59+
return start_idx
60+
return idx
61+
62+
63+
class OptimisticNonceManager(NonceManager):
64+
def __init__(
65+
self,
66+
account_index: int,
67+
api_client: api_client.ApiClient,
68+
start_api_key: int,
69+
end_api_key: Optional[int] = None,
70+
) -> None:
71+
super().__init__(account_index, api_client, start_api_key, end_api_key)
72+
73+
def next_nonce(self) -> Tuple[int, int]:
74+
self.current_api_key = increment_circular(self.current_api_key, self.start_api_key, self.end_api_key)
75+
self.nonce[self.current_api_key] += 1
76+
return (self.current_api_key, self.nonce[self.current_api_key])
77+
78+
def acknowledge_failure(self, api_key_index: int) -> None:
79+
self.nonce[api_key_index] -= 1
80+
81+
82+
class ApiNonceManager(NonceManager):
83+
def __init__(
84+
self,
85+
account_index: int,
86+
api_client: api_client.ApiClient,
87+
start_api_key: int,
88+
end_api_key: Optional[int] = None,
89+
) -> None:
90+
super().__init__(account_index, api_client, start_api_key, end_api_key)
91+
92+
def next_nonce(self) -> Tuple[int, int]:
93+
"""
94+
It is recommended to wait at least 350ms before using the same api key.
95+
Please be mindful of your transaction frequency when using this nonce manager.
96+
predicted_execution_time_ms from the response could give you a tighter bound.
97+
"""
98+
self.current_api_key = increment_circular(self.current_api_key, self.start_api_key, self.end_api_key)
99+
self.nonce[self.current_api_key] = get_nonce_from_api(self.api_client, self.account_index, self.current_api_key)
100+
return (self.current_api_key, self.nonce[self.current_api_key])
101+
102+
def refresh_nonce(self, api_key_index: int) -> int:
103+
self.nonce[api_key_index] = get_nonce_from_api(self.api_client, self.start_api_key, self.end_api_key)
104+
105+
106+
class NonceManagerType(enum.Enum):
107+
OPTIMISTIC = 1
108+
API = 2
109+
110+
111+
def nonce_manager_factory(
112+
nonce_manager_type: NonceManagerType,
113+
account_index: int,
114+
api_client: api_client.ApiClient,
115+
start_api_key: int,
116+
end_api_key: Optional[int] = None,
117+
) -> NonceManager:
118+
if nonce_manager_type == NonceManagerType.OPTIMISTIC:
119+
return OptimisticNonceManager(
120+
account_index=account_index,
121+
api_client=api_client,
122+
start_api_key=start_api_key,
123+
end_api_key=end_api_key,
124+
)
125+
elif nonce_manager_type == NonceManagerType.API:
126+
return ApiNonceManager(
127+
account_index=account_index,
128+
api_client=api_client,
129+
start_api_key=start_api_key,
130+
end_api_key=end_api_key,
131+
)
132+
raise ValidationError("invalid nonce manager type")

0 commit comments

Comments
 (0)