Skip to content

Commit 4745c60

Browse files
authored
add stress tests with malicious generators (#8570)
* add tests with malicious generators * raise limit for stress tests
1 parent 37c77b3 commit 4745c60

File tree

2 files changed

+313
-1
lines changed

2 files changed

+313
-1
lines changed

tests/check_pytest_monitor_output.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,20 @@
99
line = ln.strip().split()
1010

1111
print(f"{float(line[1]) * 100.0: 8.1f}% CPU {float(line[2]):7.1f}s {float(line[3]): 8.2f} MB RAM {line[0]}")
12-
if float(line[3]) > 800:
12+
limit = 800
13+
14+
# until this can be optimized, use higher limits
15+
if "test_duplicate_coin_announces" in line[0]:
16+
limit = 2200
17+
elif (
18+
"test_duplicate_large_integer_substr" in line[0]
19+
or "test_duplicate_reserve_fee" in line[0]
20+
or "test_duplicate_large_integer_negative" in line[0]
21+
or "test_duplicate_large_integer" in line[0]
22+
):
23+
limit = 1100
24+
25+
if float(line[3]) > limit:
1326
print(" ERROR: ^^ exceeded RAM limit ^^ \n")
1427
ret += 1
1528

tests/core/full_node/test_mempool.py

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
import logging
3+
from time import time
34

45
from typing import Dict, List, Optional, Tuple, Callable
56

@@ -1880,3 +1881,301 @@ def test_unknown_condition(self):
18801881
else:
18811882
assert npc_result.error is None
18821883
assert npc_result.npc_list[0].conditions == []
1884+
1885+
1886+
# the tests below are malicious generator programs
1887+
1888+
# this program:
1889+
# (mod (A B)
1890+
# (defun large_string (V N)
1891+
# (if N (large_string (concat V V) (- N 1)) V)
1892+
# )
1893+
# (defun iter (V N)
1894+
# (if N (c V (iter V (- N 1))) ())
1895+
# )
1896+
# (iter (c (q . 83) (c (concat (large_string 0x00 A) (q . 100)) ())) B)
1897+
# )
1898+
# with A=28 and B specified as {num}
1899+
1900+
SINGLE_ARG_INT_COND = "(a (q 2 4 (c 2 (c (c (q . {opcode}) (c (concat (a 6 (c 2 (c (q . {filler}) (c 5 ())))) (q . {val})) ())) (c 11 ())))) (c (q (a (i 11 (q 4 5 (a 4 (c 2 (c 5 (c (- 11 (q . 1)) ()))))) ()) 1) 2 (i 11 (q 2 6 (c 2 (c (concat 5 5) (c (- 11 (q . 1)) ())))) (q . 5)) 1) (q 28 {num})))" # noqa
1901+
1902+
# this program:
1903+
# (mod (A B)
1904+
# (defun large_string (V N)
1905+
# (if N (large_string (concat V V) (- N 1)) V)
1906+
# )
1907+
# (defun iter (V N)
1908+
# (if N (c (c (q . 83) (c V ())) (iter (substr V 1) (- N 1))) ())
1909+
# )
1910+
# (iter (concat (large_string 0x00 A) (q . 100)) B)
1911+
# )
1912+
# truncates the first byte of the large string being passed down for each
1913+
# iteration, in an attempt to defeat any caching of integers by node ID.
1914+
# substr is cheap, and no memory is copied, so we can perform a lot of these
1915+
SINGLE_ARG_INT_SUBSTR_COND = "(a (q 2 4 (c 2 (c (concat (a 6 (c 2 (c (q . {filler}) (c 5 ())))) (q . {val})) (c 11 ())))) (c (q (a (i 11 (q 4 (c (q . {opcode}) (c 5 ())) (a 4 (c 2 (c (substr 5 (q . 1)) (c (- 11 (q . 1)) ()))))) ()) 1) 2 (i 11 (q 2 6 (c 2 (c (concat 5 5) (c (- 11 (q . 1)) ())))) (q . 5)) 1) (q 28 {num})))" # noqa
1916+
1917+
# this program:
1918+
# (mod (A B)
1919+
# (defun large_string (V N)
1920+
# (if N (large_string (concat V V) (- N 1)) V)
1921+
# )
1922+
# (defun iter (V N)
1923+
# (if N (c (c (q . 83) (c V ())) (iter (substr V 0 (- (strlen V) 1)) (- N 1))) ())
1924+
# )
1925+
# (iter (concat (large_string 0x00 A) (q . 0xffffffff)) B)
1926+
# )
1927+
SINGLE_ARG_INT_SUBSTR_TAIL_COND = "(a (q 2 4 (c 2 (c (concat (a 6 (c 2 (c (q . {filler}) (c 5 ())))) (q . {val})) (c 11 ())))) (c (q (a (i 11 (q 4 (c (q . {opcode}) (c 5 ())) (a 4 (c 2 (c (substr 5 () (- (strlen 5) (q . 1))) (c (- 11 (q . 1)) ()))))) ()) 1) 2 (i 11 (q 2 6 (c 2 (c (concat 5 5) (c (- 11 (q . 1)) ())))) (q . 5)) 1) (q 25 {num})))" # noqa
1928+
1929+
# (mod (A B)
1930+
# (defun large_string (V N)
1931+
# (if N (large_string (concat V V) (- N 1)) V)
1932+
# )
1933+
# (defun iter (V N)
1934+
# (if N (c (c (q . 83) (c (concat V N) ())) (iter V (- N 1))) ())
1935+
# )
1936+
# (iter (large_string 0x00 A) B)
1937+
# )
1938+
SINGLE_ARG_INT_LADDER_COND = "(a (q 2 4 (c 2 (c (a 6 (c 2 (c (q . {filler}) (c 5 ())))) (c 11 ())))) (c (q (a (i 11 (q 4 (c (q . {opcode}) (c (concat 5 11) ())) (a 4 (c 2 (c 5 (c (- 11 (q . 1)) ()))))) ()) 1) 2 (i 11 (q 2 6 (c 2 (c (concat 5 5) (c (- 11 (q . 1)) ())))) (q . 5)) 1) (q 24 {num})))" # noqa
1939+
1940+
# this program:
1941+
# (mod (A B)
1942+
# (defun large_message (N)
1943+
# (lsh (q . "a") N)
1944+
# )
1945+
# (defun iter (V N)
1946+
# (if N (c V (iter V (- N 1))) ())
1947+
# )
1948+
# (iter (c (q . 60) (c (large_message A) ())) B)
1949+
# )
1950+
# with B set to {num}
1951+
1952+
CREATE_ANNOUNCE_COND = "(a (q 2 4 (c 2 (c (c (q . {opcode}) (c (a 6 (c 2 (c 5 ()))) ())) (c 11 ())))) (c (q (a (i 11 (q 4 5 (a 4 (c 2 (c 5 (c (- 11 (q . 1)) ()))))) ()) 1) 23 (q . 97) 5) (q 8184 {num})))" # noqa
1953+
1954+
# this program:
1955+
# (mod (A)
1956+
# (defun iter (V N)
1957+
# (if N (c V (iter V (- N 1))) ())
1958+
# )
1959+
# (iter (q 51 "abababababababababababababababab" 1) A)
1960+
# )
1961+
CREATE_COIN = '(a (q 2 2 (c 2 (c (q 51 "abababababababababababababababab" 1) (c 5 ())))) (c (q 2 (i 11 (q 4 5 (a 2 (c 2 (c 5 (c (- 11 (q . 1)) ()))))) ()) 1) (q {num})))' # noqa
1962+
1963+
# this program:
1964+
# (mod (A)
1965+
# (defun append (L B)
1966+
# (if L
1967+
# (c (f L) (append (r L) B))
1968+
# (c B ())
1969+
# )
1970+
# )
1971+
# (defun iter (V N)
1972+
# (if N (c (append V N) (iter V (- N 1))) ())
1973+
# )
1974+
# (iter (q 51 "abababababababababababababababab") A)
1975+
# )
1976+
# creates {num} CREATE_COIN conditions, each with a different amount
1977+
CREATE_UNIQUE_COINS = '(a (q 2 6 (c 2 (c (q 51 "abababababababababababababababab") (c 5 ())))) (c (q (a (i 5 (q 4 9 (a 4 (c 2 (c 13 (c 11 ()))))) (q 4 11 ())) 1) 2 (i 11 (q 4 (a 4 (c 2 (c 5 (c 11 ())))) (a 6 (c 2 (c 5 (c (- 11 (q . 1)) ()))))) ()) 1) (q {num})))' # noqa
1978+
1979+
1980+
class TestMaliciousGenerators:
1981+
1982+
# TODO: create a lot of announcements. The messages can be made different by
1983+
# using substr on a large buffer
1984+
1985+
# for all the height/time locks, we should only return the most strict
1986+
# condition, not all of them
1987+
@pytest.mark.parametrize(
1988+
"opcode",
1989+
[
1990+
ConditionOpcode.ASSERT_HEIGHT_ABSOLUTE,
1991+
ConditionOpcode.ASSERT_HEIGHT_RELATIVE,
1992+
ConditionOpcode.ASSERT_SECONDS_ABSOLUTE,
1993+
ConditionOpcode.ASSERT_SECONDS_RELATIVE,
1994+
],
1995+
)
1996+
def test_duplicate_large_integer_ladder(self, opcode):
1997+
condition = SINGLE_ARG_INT_LADDER_COND.format(opcode=opcode.value[0], num=28, filler="0x00")
1998+
start_time = time()
1999+
npc_result = generator_condition_tester(condition, quote=False)
2000+
run_time = time() - start_time
2001+
assert npc_result.error is None
2002+
assert len(npc_result.npc_list) == 1
2003+
assert npc_result.npc_list[0].conditions == [
2004+
(
2005+
opcode,
2006+
[ConditionWithArgs(opcode, [int_to_bytes(28)])],
2007+
)
2008+
]
2009+
assert run_time < 1
2010+
print(f"run time:{run_time}")
2011+
2012+
@pytest.mark.parametrize(
2013+
"opcode",
2014+
[
2015+
ConditionOpcode.ASSERT_HEIGHT_ABSOLUTE,
2016+
ConditionOpcode.ASSERT_HEIGHT_RELATIVE,
2017+
ConditionOpcode.ASSERT_SECONDS_ABSOLUTE,
2018+
ConditionOpcode.ASSERT_SECONDS_RELATIVE,
2019+
],
2020+
)
2021+
def test_duplicate_large_integer(self, opcode):
2022+
condition = SINGLE_ARG_INT_COND.format(opcode=opcode.value[0], num=280000, val=100, filler="0x00")
2023+
start_time = time()
2024+
npc_result = generator_condition_tester(condition, quote=False)
2025+
run_time = time() - start_time
2026+
assert npc_result.error is None
2027+
assert len(npc_result.npc_list) == 1
2028+
assert npc_result.npc_list[0].conditions == [
2029+
(
2030+
opcode,
2031+
[ConditionWithArgs(opcode, [bytes([100])])],
2032+
)
2033+
]
2034+
assert run_time < 2
2035+
print(f"run time:{run_time}")
2036+
2037+
@pytest.mark.parametrize(
2038+
"opcode",
2039+
[
2040+
ConditionOpcode.ASSERT_HEIGHT_ABSOLUTE,
2041+
ConditionOpcode.ASSERT_HEIGHT_RELATIVE,
2042+
ConditionOpcode.ASSERT_SECONDS_ABSOLUTE,
2043+
ConditionOpcode.ASSERT_SECONDS_RELATIVE,
2044+
],
2045+
)
2046+
def test_duplicate_large_integer_substr(self, opcode):
2047+
condition = SINGLE_ARG_INT_SUBSTR_COND.format(opcode=opcode.value[0], num=280000, val=100, filler="0x00")
2048+
start_time = time()
2049+
npc_result = generator_condition_tester(condition, quote=False)
2050+
run_time = time() - start_time
2051+
assert npc_result.error is None
2052+
assert len(npc_result.npc_list) == 1
2053+
assert npc_result.npc_list[0].conditions == [
2054+
(
2055+
opcode,
2056+
[ConditionWithArgs(opcode, [bytes([100])])],
2057+
)
2058+
]
2059+
assert run_time < 3
2060+
print(f"run time:{run_time}")
2061+
2062+
@pytest.mark.parametrize(
2063+
"opcode",
2064+
[
2065+
ConditionOpcode.ASSERT_HEIGHT_ABSOLUTE,
2066+
ConditionOpcode.ASSERT_HEIGHT_RELATIVE,
2067+
ConditionOpcode.ASSERT_SECONDS_ABSOLUTE,
2068+
ConditionOpcode.ASSERT_SECONDS_RELATIVE,
2069+
],
2070+
)
2071+
def test_duplicate_large_integer_substr_tail(self, opcode):
2072+
condition = SINGLE_ARG_INT_SUBSTR_TAIL_COND.format(
2073+
opcode=opcode.value[0], num=280, val="0xffffffff", filler="0x00"
2074+
)
2075+
start_time = time()
2076+
npc_result = generator_condition_tester(condition, quote=False)
2077+
run_time = time() - start_time
2078+
assert npc_result.error is None
2079+
assert len(npc_result.npc_list) == 1
2080+
2081+
print(npc_result.npc_list[0].conditions[0][1])
2082+
assert ConditionWithArgs(opcode, [int_to_bytes(0xFFFFFFFF)]) in npc_result.npc_list[0].conditions[0][1]
2083+
assert run_time < 1
2084+
print(f"run time:{run_time}")
2085+
2086+
@pytest.mark.parametrize(
2087+
"opcode",
2088+
[
2089+
ConditionOpcode.ASSERT_HEIGHT_ABSOLUTE,
2090+
ConditionOpcode.ASSERT_HEIGHT_RELATIVE,
2091+
ConditionOpcode.ASSERT_SECONDS_ABSOLUTE,
2092+
ConditionOpcode.ASSERT_SECONDS_RELATIVE,
2093+
],
2094+
)
2095+
def test_duplicate_large_integer_negative(self, opcode):
2096+
condition = SINGLE_ARG_INT_COND.format(opcode=opcode.value[0], num=280000, val=100, filler="0xff")
2097+
start_time = time()
2098+
npc_result = generator_condition_tester(condition, quote=False)
2099+
run_time = time() - start_time
2100+
assert npc_result.error is None
2101+
assert len(npc_result.npc_list) == 1
2102+
assert npc_result.npc_list[0].conditions == []
2103+
assert run_time < 2
2104+
print(f"run time:{run_time}")
2105+
2106+
def test_duplicate_reserve_fee(self):
2107+
opcode = ConditionOpcode.RESERVE_FEE
2108+
condition = SINGLE_ARG_INT_COND.format(opcode=opcode.value[0], num=280000, val=100, filler="0x00")
2109+
start_time = time()
2110+
npc_result = generator_condition_tester(condition, quote=False)
2111+
run_time = time() - start_time
2112+
assert npc_result.error is None
2113+
assert len(npc_result.npc_list) == 1
2114+
assert npc_result.npc_list[0].conditions == [
2115+
(
2116+
opcode.value,
2117+
[ConditionWithArgs(opcode, [int_to_bytes(100 * 280000)])],
2118+
)
2119+
]
2120+
assert run_time < 2
2121+
print(f"run time:{run_time}")
2122+
2123+
def test_duplicate_reserve_fee_negative(self):
2124+
opcode = ConditionOpcode.RESERVE_FEE
2125+
condition = SINGLE_ARG_INT_COND.format(opcode=opcode.value[0], num=200000, val=100, filler="0xff")
2126+
start_time = time()
2127+
npc_result = generator_condition_tester(condition, quote=False)
2128+
run_time = time() - start_time
2129+
# RESERVE_FEE conditions fail unconditionally if they have a negative
2130+
# amount
2131+
assert npc_result.error == Err.RESERVE_FEE_CONDITION_FAILED.value
2132+
assert len(npc_result.npc_list) == 0
2133+
assert run_time < 1
2134+
print(f"run time:{run_time}")
2135+
2136+
@pytest.mark.parametrize(
2137+
"opcode", [ConditionOpcode.CREATE_COIN_ANNOUNCEMENT, ConditionOpcode.CREATE_PUZZLE_ANNOUNCEMENT]
2138+
)
2139+
def test_duplicate_coin_announces(self, opcode):
2140+
condition = CREATE_ANNOUNCE_COND.format(opcode=opcode.value[0], num=5950000)
2141+
start_time = time()
2142+
npc_result = generator_condition_tester(condition, quote=False)
2143+
run_time = time() - start_time
2144+
assert npc_result.error is None
2145+
assert len(npc_result.npc_list) == 1
2146+
# coin announcements are not propagated to python, but validated in rust
2147+
assert len(npc_result.npc_list[0].conditions) == 0
2148+
# TODO: optimize clvm to make this run in < 1 second
2149+
assert run_time < 13
2150+
print(f"run time:{run_time}")
2151+
2152+
def test_create_coin_duplicates(self):
2153+
# CREATE_COIN
2154+
# this program will emit 6000 identical CREATE_COIN conditions. However,
2155+
# we'll just end up looking at two of them, and fail at the first
2156+
# duplicate
2157+
condition = CREATE_COIN.format(num=600000)
2158+
start_time = time()
2159+
npc_result = generator_condition_tester(condition, quote=False)
2160+
run_time = time() - start_time
2161+
assert npc_result.error == Err.DUPLICATE_OUTPUT.value
2162+
assert len(npc_result.npc_list) == 0
2163+
assert run_time < 2
2164+
print(f"run time:{run_time}")
2165+
2166+
def test_many_create_coin(self):
2167+
# CREATE_COIN
2168+
# this program will emit many CREATE_COIN conditions, all with different
2169+
# amounts.
2170+
# the number 6095 was chosen carefully to not exceed the maximum cost
2171+
condition = CREATE_UNIQUE_COINS.format(num=6094)
2172+
start_time = time()
2173+
npc_result = generator_condition_tester(condition, quote=False)
2174+
run_time = time() - start_time
2175+
assert npc_result.error is None
2176+
assert len(npc_result.npc_list) == 1
2177+
assert len(npc_result.npc_list[0].conditions) == 1
2178+
assert npc_result.npc_list[0].conditions[0][0] == ConditionOpcode.CREATE_COIN.value
2179+
assert len(npc_result.npc_list[0].conditions[0][1]) == 6094
2180+
assert run_time < 1
2181+
print(f"run time:{run_time}")

0 commit comments

Comments
 (0)