Skip to content

Commit a4b9f0e

Browse files
committed
feat: check submited signature against finalization
1 parent 106de80 commit a4b9f0e

File tree

4 files changed

+77
-22
lines changed

4 files changed

+77
-22
lines changed

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,24 @@ todos:
2929
- [ ] check node uptime
3030
- ftso:
3131
- [ ] better ftso value analysis
32+
- if you are meeting minimal conditions
3233
- weird value (not just None but also 0.1 all the time, or just wildly different to median)
3334
- parse events to be able to tell feeds by names not by indices
34-
- [ ] check submit signatures signature against finalization
35+
- [x] check submit signatures signature against finalization
3536
- fdc:
37+
- [ ] sample minimal conditions
3638
- [ ] correct bitvote length (submit2 fdc)
37-
- [ ] check submit signatures signature against finalization
39+
- [x] check submit signatures signature against finalization
3840
- fast updates:
3941
- [ ] recover signature from fast updates and check if updates are being made
4042
- [ ] check if length of update is correct
43+
- [ ] sample minimal conditions
4144
- push notification scheme:
4245
- we need a general-ish extnesible framework to add more notification plugins
4346
- notification plugins
4447
- [x] stdout logging
4548
- [x] discord
4649
- [ ] slack
50+
- [ ] telegram
51+
- [ ] pager duty
52+
- [ ] generic post

observer/observer.py

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import enum
22
import logging
33
import time
4+
from typing import Self
45

56
import requests
67
from attrs import define
8+
from eth_account._utils.signing import to_standard_v
9+
from eth_keys.datatypes import Signature as EthSignature
710
from py_flare_common.fsp.epoch.epoch import RewardEpoch
811
from py_flare_common.fsp.messaging import (
912
parse_generic_tx,
@@ -13,6 +16,7 @@
1316
)
1417
from py_flare_common.fsp.messaging.byte_parser import ByteParser
1518
from py_flare_common.fsp.messaging.types import ParsedPayload
19+
from py_flare_common.fsp.messaging.types import Signature as SSignature
1620
from py_flare_common.ftso.commit import commit_hash
1721
from web3 import AsyncWeb3
1822
from web3._utils.events import get_event_data
@@ -41,9 +45,22 @@
4145
LOGGER.info("initialized")
4246

4347

48+
class Signature(EthSignature):
49+
@classmethod
50+
def from_vrs(cls, s: SSignature) -> Self:
51+
return cls(
52+
vrs=(
53+
to_standard_v(int(s.v, 16)),
54+
int(s.r, 16),
55+
int(s.s, 16),
56+
)
57+
)
58+
59+
4460
def notify_discord(config: Configuration, message: str) -> None:
4561
if config.discord_webhook is None:
4662
return
63+
4764
requests.post(
4865
config.discord_webhook,
4966
headers={"Content-Type": "application/json"},
@@ -242,7 +259,7 @@ def validate_ftso(round: VotingRound, entity: Entity):
242259
issues.append(
243260
Issue(
244261
IssueLevel.INFO,
245-
f"no submit1 transaction for ftso in round {round.voting_epoch.id}",
262+
f"no submit1 transaction for ftso in round {epoch.id}",
246263
)
247264
)
248265

@@ -270,7 +287,7 @@ def validate_ftso(round: VotingRound, entity: Entity):
270287
IssueLevel.INFO,
271288
(
272289
"submit 2 had 'None' value for feeds on indices "
273-
f"{', '.join(indices)} in round {round.voting_epoch.id}"
290+
f"{', '.join(indices)} in round {epoch.id}"
274291
),
275292
),
276293
)
@@ -290,7 +307,7 @@ def validate_ftso(round: VotingRound, entity: Entity):
290307
IssueLevel.INFO,
291308
(
292309
"commit hash and reveal didn't match in round "
293-
f"{round.voting_epoch.id}, causing reveal offence"
310+
f"{epoch.id}, causing reveal offence"
294311
),
295312
),
296313
)
@@ -300,23 +317,34 @@ def validate_ftso(round: VotingRound, entity: Entity):
300317
Issue(
301318
# TODO:(matej) change level to warning
302319
IssueLevel.INFO,
303-
(
304-
"no submit signatures transaction for ftso in round "
305-
f"{round.voting_epoch.id}"
306-
),
320+
("no submit signatures transaction for ftso in round " f"{epoch.id}"),
307321
),
308322
)
309323

310324
if finalization and ss:
311-
# TODO:(matej) check for correct signature
312-
...
325+
s = Signature.from_vrs(submit_sig[0].payload.signature)
326+
addr = s.recover_public_key_from_msg_hash(
327+
finalization.to_message()
328+
).to_checksum_address()
329+
330+
if addr != entity.signing_policy_address:
331+
issues.append(
332+
Issue(
333+
# TODO:(matej) change level to warning
334+
IssueLevel.INFO,
335+
(
336+
"submit signatures signature doesn't match finalization for "
337+
f"ftso in round {epoch.id}"
338+
),
339+
),
340+
)
313341

314342
return issues
315343

316344

317345
def validate_fdc(round: VotingRound, entity: Entity):
318346
epoch = round.voting_epoch
319-
fdc = round.ftso
347+
fdc = round.fdc
320348
finalization = fdc.finalization
321349

322350
_submit1 = fdc.submit_1.by_identity.get(entity.identity_address, [])
@@ -379,16 +407,27 @@ def validate_fdc(round: VotingRound, entity: Entity):
379407
Issue(
380408
# TODO:(matej) change level to critical
381409
IssueLevel.INFO,
382-
(
383-
"no submit signatures transaction for fdc in round "
384-
f"{round.voting_epoch.id}"
385-
),
410+
("no submit signatures transaction for fdc in round " f"{epoch.id}"),
386411
),
387412
)
388413

389414
if finalization and ss:
390-
# TODO:(matej) check for correct signature
391-
...
415+
s = Signature.from_vrs(submit_sig[0].payload.signature)
416+
addr = s.recover_public_key_from_msg_hash(
417+
finalization.to_message()
418+
).to_checksum_address()
419+
420+
if addr != entity.signing_policy_address:
421+
issues.append(
422+
Issue(
423+
# TODO:(matej) change level to warning
424+
IssueLevel.INFO,
425+
(
426+
"submit signatures signature doesn't match finalization for "
427+
f"fdc in round {epoch.id}"
428+
),
429+
),
430+
)
392431

393432
return issues
394433

observer/types.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from typing import Any, Self
22

33
from attrs import frozen
4+
from eth_account.messages import _hash_eip191_message, encode_defunct
45
from eth_typing import ChecksumAddress
6+
from eth_utils.crypto import keccak
57
from web3.types import BlockData
68

7-
from observer.utils import prefix_0x
8-
99

1010
@frozen
1111
class ProtocolMessageRelayed:
@@ -15,6 +15,16 @@ class ProtocolMessageRelayed:
1515
merkle_root: str
1616
timestamp: int
1717

18+
def to_message(self) -> bytes:
19+
message = (
20+
self.protocol_id.to_bytes(1, "big")
21+
+ self.voting_round_id.to_bytes(4, "big")
22+
+ self.is_secure_random.to_bytes(1, "big")
23+
+ bytes.fromhex(self.merkle_root)
24+
)
25+
26+
return _hash_eip191_message(encode_defunct(keccak(message)))
27+
1828
@classmethod
1929
def from_dict(cls, d: dict[str, Any], block_data: BlockData) -> Self:
2030
assert "timestamp" in block_data
@@ -23,7 +33,7 @@ def from_dict(cls, d: dict[str, Any], block_data: BlockData) -> Self:
2333
protocol_id=int(d["protocolId"]),
2434
voting_round_id=int(d["votingRoundId"]),
2535
is_secure_random=d["isSecureRandom"],
26-
merkle_root=prefix_0x(d["merkleRoot"].hex()),
36+
merkle_root=d["merkleRoot"].hex(),
2737
timestamp=block_data["timestamp"],
2838
)
2939

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ requests==2.32.3
22
py-flare-common==0.1.6
33
attrs==24.2.0
44
python-dotenv==1.0.1
5-
web3==7.6.0
5+
web3==7.11.1

0 commit comments

Comments
 (0)