Skip to content

Commit

Permalink
feat: allow outputting indirect dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
Otto-AA committed Aug 10, 2024
1 parent ce28501 commit 9e2d6a2
Show file tree
Hide file tree
Showing 6 changed files with 585 additions and 23 deletions.

Large diffs are not rendered by default.

22 changes: 21 additions & 1 deletion tests/integration/snapshots/snap_test_snapshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
"tx_b": "0x6016a7e14ef9b7ecc80985ace338e8894de892a58e941dd45dbb90c729079cb4",
},
{
"filter": "collision",
"filter": "no collision",
"tx_a": "0x40ca117ccc4933dd5b30e399f64673068c622802bdbd728f84b212cd197bf51a",
"tx_b": "0x31c5b4782a75a1f5f513cd86ede1a8c0af54baa9511982f43a57988036ed0fed",
},
Expand Down Expand Up @@ -129,3 +129,23 @@
"tx_b": "0xd2b8e8cff425ae336c6084a9d7fc1c7d33d599fdf00c93eda40339e37ca6b12d",
},
]

snapshots[
"test_tod_attack_miner_evaluation_indirect_dependencies indirect dependencies"
] = [
(
"0x1b99d2ee257a00b5196b39534366fde8fded0cd3bfe639f161d12a9ca011adaf",
"0x9213ae52ca8bf02107081b9340dd0d03ff533ac1040abeb873b686ed6427b303",
"0x1b99d2ee257a00b5196b39534366fde8fded0cd3bfe639f161d12a9ca011adaf|0xb8ded758b726cc239303cc46de702f9ea752f4fb2de055cb9a07c4897025caac|0x9213ae52ca8bf02107081b9340dd0d03ff533ac1040abeb873b686ed6427b303",
),
(
"0x775232180c49821d4208b3c5470d6367de5b96810c79ae0993aeb98ada762ee8",
"0x94029b952ede83cd26f48ab40fad24851a517696b2785b631cdacb02795934cf",
"0x775232180c49821d4208b3c5470d6367de5b96810c79ae0993aeb98ada762ee8|0x5ced0a26b13e6ae62415e68e6aae4fe03da5a2179916809345fda2bdfc62ebfe|0x94029b952ede83cd26f48ab40fad24851a517696b2785b631cdacb02795934cf",
),
(
"0xf812335569705e032f8eca9fff94d3348e29d748bd9ed4e2acd76fe54dbec42d",
"0xd2b8e8cff425ae336c6084a9d7fc1c7d33d599fdf00c93eda40339e37ca6b12d",
"0xf812335569705e032f8eca9fff94d3348e29d748bd9ed4e2acd76fe54dbec42d|0x69b21c9878b2f517e3d5d882ab0cf29e150435f5c5b529d52e1480ae7af64509|0xd2b8e8cff425ae336c6084a9d7fc1c7d33d599fdf00c93eda40339e37ca6b12d",
),
]
18 changes: 18 additions & 0 deletions tests/integration/test_snapshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from tod_attack_miner.db.filters import (
get_filters_duplicate_limits,
get_filters_except_duplicate_limits,
get_filters_up_to_indirect_dependencies,
)
from tod_attack_miner.fetcher.fetcher import BlockRange
from tod_attack_miner.miner.miner import Miner
Expand Down Expand Up @@ -95,3 +96,20 @@ def test_tod_attack_miner_evaluation(
)

snapshot.assert_match(results, "evaluation results")


@pytest.mark.vcr
def test_tod_attack_miner_evaluation_indirect_dependencies(
postgresql: Connection, snapshot: PyTestSnapshotTest
):
block_range = BlockRange(19895500, 19895504)

miner = Miner(RPC(test_provider_url), DB(postgresql))

miner.fetch(block_range.start, block_range.end)
miner.find_collisions()
results = miner.get_indirect_dependencies(
get_filters_up_to_indirect_dependencies(3), evaluation_candidates, 1
)

snapshot.assert_match(results, "indirect dependencies")
54 changes: 33 additions & 21 deletions tod_attack_miner/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from tod_attack_miner.db.filters import (
get_filters_duplicate_limits,
get_filters_except_duplicate_limits,
get_filters_up_to_indirect_dependencies,
)
from tod_attack_miner.miner.miner import Miner

Expand All @@ -25,7 +26,7 @@ def main():
)
parser.add_argument("--archive-node-provider", default="http://localhost:8124/eth")
parser.add_argument("--from-block", default=19895500)
parser.add_argument("--to-block", default=19895500 + 100 - 1)
parser.add_argument("--to-block", default=19895504)
parser.add_argument(
"--window-size",
type=int,
Expand All @@ -48,6 +49,11 @@ def main():
default=Path("evaluations.csv"),
help="Path, where evaluation results should be stored",
)
parser.add_argument(
"--extract-indirect-dependencies",
action="store_true",
help="When evaluating candidates, stop before the indirect dependencies filter and output indirect dependency paths",
)
parser.add_argument("--postgres-user", type=str, default="postgres")
parser.add_argument("--postgres-password", type=str, default="password")
parser.add_argument("--postgres-host", type=str, default="localhost")
Expand All @@ -63,6 +69,7 @@ def main():
args = parser.parse_args()
evaluate_candidates_csv: Path | None = args.evaluate_candidates_csv
evaluation_results_csv: Path = args.evaluation_result_csv
extract_indirect_dependencies: bool = args.extract_indirect_dependencies

with psycopg.connect(
f"user={args.postgres_user} password={args.postgres_password} host={args.postgres_host} port={args.postgres_port}"
Expand All @@ -79,30 +86,35 @@ def main():
) as results_csv_file:
csv_reader = csv.DictReader(csv_file)
candidates = [(c["tx_a"], c["tx_b"]) for c in csv_reader]
if args.reset_db:
miner.reset_db()
miner.fetch(int(args.from_block), int(args.to_block))
miner.find_collisions()
results = miner.evaluate_candidates(
get_filters_except_duplicate_limits(25)
+ get_filters_duplicate_limits(10),
candidates,
)
if extract_indirect_dependencies:
filters = get_filters_up_to_indirect_dependencies(25)
results = miner.get_indirect_dependencies(filters, candidates, 1)
csv_writer = csv.writer(results_csv_file)
csv_writer.writerow(("tx_a", "tx_b", "dependency_path"))
csv_writer.writerows(results)
else:
filters = get_filters_except_duplicate_limits(
25
) + get_filters_duplicate_limits(10)
results = miner.evaluate_candidates(filters, candidates)

csv_writer = csv.DictWriter(
results_csv_file, ["tx_a", "tx_b", "filtered_by"]
)
csv_writer.writeheader()
rows = [
{
"tx_a": c["tx_a"],
"tx_b": c["tx_b"],
"filtered_by": c["filter"] or "",
}
for c in results
]
csv_writer.writerows(rows)
csv_writer = csv.DictWriter(
results_csv_file, ["tx_a", "tx_b", "filtered_by"]
)
csv_writer.writeheader()
rows = [
{
"tx_a": c["tx_a"],
"tx_b": c["tx_b"],
"filtered_by": c["filter"] or "",
}
for c in results
]
csv_writer.writerows(rows)
print(f"Saved results to {evaluation_results_csv}")

else:
if args.reset_db:
miner.reset_db()
Expand Down
40 changes: 39 additions & 1 deletion tod_attack_miner/db/filters.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Callable
from typing import Callable, Iterable
import psycopg
import psycopg.sql
from tod_attack_miner.db.db import DB
Expand Down Expand Up @@ -126,6 +126,33 @@ def filter_indirect_dependencies_recursive(db: DB):
return deleted


def get_evaluation_indirect_dependencies_recursive(
db: DB, max_depth: int
) -> Iterable[tuple[str, str, str]]:
sql = psycopg.sql.SQL("""
WITH RECURSIVE depends_on(tx_a, tx_b, min_block_number, min_tx_index, tx_x, path, depth) AS (
SELECT tx_write_hash, tx_access_hash, block_number, tx_index, tx_access_hash, tx_access_hash, 1
FROM candidates
INNER JOIN transactions ON hash = tx_write_hash
UNION
SELECT tx_a, tx_b, min_block_number, min_tx_index, tx_write_hash, tx_write_hash || '|' || path, depth + 1
FROM depends_on, candidates
INNER JOIN transactions ON hash = tx_write_hash
WHERE depends_on.tx_x = tx_access_hash
AND depth <= {}
AND (block_number > min_block_number
OR block_number = min_block_number AND tx_index > min_tx_index)
)
SELECT tx_a, tx_b, tx_a || '|' || path
FROM depends_on
INNER JOIN evaluation_candidates
ON tx_a = tx_write_hash AND tx_b = tx_access_hash
WHERE tx_b != tx_x
""").format(max_depth)
with db._con.cursor() as cursor:
return cursor.execute(sql).fetchall()


def create_limit_collisions_per_address(limit: int):
def limit_collisions_per_address(db: DB):
sql = f"""
Expand Down Expand Up @@ -209,6 +236,17 @@ def get_filters_except_duplicate_limits(
]


def get_filters_up_to_indirect_dependencies(
window_size: int | None,
) -> list[tuple[str, Callable[[DB], int]]]:
return [
("block_window", create_block_window_filter(window_size)),
("block_producers", filter_block_producers),
("nonces", filter_nonces),
("codes", filter_codes),
]


def get_filters_duplicate_limits(limit: int) -> list[tuple[str, Callable[[DB], int]]]:
return [
("limited_collisions_per_address", create_limit_collisions_per_address(limit)),
Expand Down
7 changes: 7 additions & 0 deletions tod_attack_miner/miner/miner.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import Callable, Iterable, Sequence
from tod_attack_miner.db.db import DB, Candidate, EvaluationCandidate
from tod_attack_miner.db.filters import get_evaluation_indirect_dependencies_recursive
from tod_attack_miner.fetcher.fetcher import BlockRange, fetch_block_range
from tod_attack_miner.rpc.rpc import RPC

Expand Down Expand Up @@ -48,6 +49,12 @@ def evaluate_candidates(
self._filter_stats["candidates"]["final"] = self.db.count_candidates()
return self.db.get_evaluation_candidates()

def get_indirect_dependencies(
self, filters: Filters, candidates: Iterable[tuple[str, str]], max_depth: int
) -> Iterable[tuple[str, str, str]]:
self.evaluate_candidates(filters, candidates)
return get_evaluation_indirect_dependencies_recursive(self.db, max_depth)

def count_candidates(self) -> int:
return self.db.count_candidates()

Expand Down

0 comments on commit 9e2d6a2

Please sign in to comment.