Skip to content

Commit 5c82bba

Browse files
authored
fuzz tests debug fix (#352)
* add tests * run traces in fuzzer * add tests * move tests * state root wrong * a temp fix for state root * remove bad test * test fuzzer with target using traces * pas all tests * fix tests * comment
1 parent af446d5 commit 5c82bba

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+7003
-1219
lines changed

Blockchain/Sources/Blockchain/State/State.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,19 @@ public struct State: Sendable {
238238

239239
public var stateRoot: Data32 {
240240
get async {
241-
await backend.rootHash
241+
// TODO: should use backend.rootHash after StateTrie is fixed
242+
do {
243+
let allKeys = try await backend.getKeys(nil, nil, nil)
244+
var kv: [Data31: Data] = [:]
245+
for (key, value) in allKeys {
246+
if let key31 = Data31(key) {
247+
kv[key31] = value
248+
}
249+
}
250+
return try stateMerklize(kv: kv)
251+
} catch {
252+
return await backend.rootHash
253+
}
242254
}
243255
}
244256

Boka/Sources/Fuzz.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,14 @@ extension Boka.Fuzz {
5858
@Option(help: "JAM Protocol configuration preset.")
5959
var config: JamConfig = .tiny
6060

61-
@Option(name: .long, help: "Random seed for deterministic testing. Default is random")
61+
@Option(name: .long, help: "Random seed for deterministic testing. Default is random.")
6262
var seed: UInt64 = .random(in: 0 ... UInt64.max)
6363

6464
@Option(name: .long, help: "Number of blocks to process.")
65-
var blocks: Int = 100
65+
var blocks: Int = 200
66+
67+
@Option(name: .long, help: "Directory containing traces test vectors to run.")
68+
var tracesDir: String?
6669

6770
func run() async throws {
6871
let env = ProcessInfo.processInfo.environment
@@ -76,9 +79,9 @@ extension Boka.Fuzz {
7679
socketPath: socketPath,
7780
config: config.rawValue,
7881
seed: seed,
79-
blockCount: blocks
82+
blockCount: blocks,
83+
tracesDir: tracesDir
8084
)
81-
8285
try await fuzzer.run()
8386
}
8487
}

Fuzzing/Package.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ let package = Package(
1818
.package(path: "../Codec"),
1919
.package(path: "../TracingUtils"),
2020
.package(path: "../Utils"),
21+
.package(path: "../JAMTests"),
2122
.package(url: "https://github.com/apple/swift-testing.git", branch: "6.0.0"),
2223
],
2324
targets: [
@@ -28,6 +29,7 @@ let package = Package(
2829
"Codec",
2930
"TracingUtils",
3031
"Utils",
32+
"JAMTests",
3133
],
3234
swiftSettings: [
3335
.interoperabilityMode(.Cxx),

Fuzzing/Sources/Fuzzing/FuzzGenerator/FuzzGenerator.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,21 @@ import Utils
55

66
/// Protocol for generating fuzzing test data
77
public protocol FuzzGenerator {
8-
/// Generate initial state for fuzzing
8+
/// Generate initial pre-state for fuzzing
99
/// - Parameters:
1010
/// - timeslot: The timeslot for which to generate the state
1111
/// - config: Protocol configuration
12-
/// - Returns: Array of fuzz key-value pairs representing the state
13-
func generateState(timeslot: TimeslotIndex, config: ProtocolConfigRef) async throws -> [FuzzKeyValue]
12+
/// - Returns: Tuple containing state root and array of fuzz key-value pairs representing the pre-state
13+
func generatePreState(timeslot: TimeslotIndex, config: ProtocolConfigRef) async throws
14+
-> (stateRoot: Data32, keyValues: [FuzzKeyValue])
15+
16+
/// Generate expected post-state after block execution
17+
/// - Parameters:
18+
/// - timeslot: The timeslot for which to generate the state
19+
/// - config: Protocol configuration
20+
/// - Returns: Tuple containing state root and array of fuzz key-value pairs representing the expected post-state
21+
func generatePostState(timeslot: TimeslotIndex, config: ProtocolConfigRef) async throws
22+
-> (stateRoot: Data32, keyValues: [FuzzKeyValue])
1423

1524
/// Generate a block for the given timeslot and state
1625
/// - Parameters:

Fuzzing/Sources/Fuzzing/FuzzGenerator/FuzzGeneratorRandom.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,19 @@ public class FuzzGeneratorRandom: FuzzGenerator {
4848
dataProvider = nil
4949
}
5050

51-
// TODO: Implement state generation logic
52-
public func generateState(timeslot _: TimeslotIndex, config _: ProtocolConfigRef) async throws -> [FuzzKeyValue] {
51+
// TODO: Implement pre-state generation logic
52+
public func generatePreState(
53+
timeslot _: TimeslotIndex,
54+
config _: ProtocolConfigRef
55+
) async throws -> (stateRoot: Data32, keyValues: [FuzzKeyValue]) {
56+
fatalError("not implemented")
57+
}
58+
59+
// TODO: Implement post-state generation logic
60+
public func generatePostState(
61+
timeslot _: TimeslotIndex,
62+
config _: ProtocolConfigRef
63+
) async throws -> (stateRoot: Data32, keyValues: [FuzzKeyValue]) {
5364
fatalError("not implemented")
5465
}
5566

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import Blockchain
2+
import Foundation
3+
import JAMTests
4+
import TracingUtils
5+
import Utils
6+
7+
private let logger = Logger(label: "FuzzGeneratorTraces")
8+
9+
/// A fuzz generator that loads test cases from JAM conformance fuzz traces
10+
public class FuzzGeneratorTraces: FuzzGenerator {
11+
private let testCases: [JamTestnetTestcase]
12+
13+
public init(tracesDir: String) throws {
14+
logger.info("Loading test vectors from directory: \(tracesDir)")
15+
16+
testCases = try Self.loadTestCases(from: tracesDir)
17+
18+
logger.info("Loaded \(testCases.count) test cases")
19+
20+
if testCases.isEmpty {
21+
throw FuzzGeneratorError.invalidTestData("No test cases found in directory: \(tracesDir)")
22+
}
23+
}
24+
25+
public func generatePreState(
26+
timeslot: TimeslotIndex,
27+
config _: ProtocolConfigRef
28+
) async throws -> (stateRoot: Data32, keyValues: [FuzzKeyValue]) {
29+
let testIndex = Int(timeslot)
30+
guard testIndex >= 0, testIndex < testCases.count else {
31+
throw FuzzGeneratorError.stateGenerationFailed("No test case available for timeslot \(timeslot) (index \(testIndex))")
32+
}
33+
34+
let testCase = testCases[testIndex]
35+
36+
logger.debug("Generating pre-state for timeslot \(timeslot) (test case \(testIndex + 1)/\(testCases.count))")
37+
38+
let keyValues = testCase.preState.keyvals.map { keyval in
39+
FuzzKeyValue(key: keyval.key, value: keyval.value)
40+
}
41+
42+
return (stateRoot: testCase.preState.root, keyValues: keyValues)
43+
}
44+
45+
public func generatePostState(
46+
timeslot: TimeslotIndex,
47+
config _: ProtocolConfigRef
48+
) async throws -> (stateRoot: Data32, keyValues: [FuzzKeyValue]) {
49+
let testIndex = Int(timeslot)
50+
guard testIndex >= 0, testIndex < testCases.count else {
51+
throw FuzzGeneratorError.stateGenerationFailed("No test case available for timeslot \(timeslot) (index \(testIndex))")
52+
}
53+
54+
let testCase = testCases[testIndex]
55+
56+
logger.debug("Generating expected post-state for timeslot \(timeslot) (test case \(testIndex + 1)/\(testCases.count))")
57+
58+
let keyValues = testCase.postState.keyvals.map { keyval in
59+
FuzzKeyValue(key: keyval.key, value: keyval.value)
60+
}
61+
62+
return (stateRoot: testCase.postState.root, keyValues: keyValues)
63+
}
64+
65+
public func generateBlock(timeslot: UInt32, currentStateRef _: StateRef, config _: ProtocolConfigRef) async throws -> BlockRef {
66+
let testIndex = Int(timeslot)
67+
guard testIndex >= 0, testIndex < testCases.count else {
68+
throw FuzzGeneratorError.blockGenerationFailed("No test case available for timeslot \(timeslot) (index \(testIndex))")
69+
}
70+
71+
let testCase = testCases[testIndex]
72+
73+
logger.debug("Generating block for timeslot \(timeslot) (test case \(testIndex + 1)/\(testCases.count))")
74+
75+
let block = testCase.block
76+
77+
return block.asRef()
78+
}
79+
80+
private static func loadTestCases(from directory: String) throws -> [JamTestnetTestcase] {
81+
logger.info("Loading test cases from directory: \(directory)")
82+
83+
let basePath = directory
84+
85+
guard FileManager.default.fileExists(atPath: basePath) else {
86+
throw FuzzGeneratorError.invalidTestData("Directory does not exist: \(basePath)")
87+
}
88+
89+
var allDecodedTestCases: [JamTestnetTestcase] = []
90+
91+
// Find all .bin files with depth of 2
92+
func findBinFiles(in path: String, currentDepth: Int = 0) throws -> [String] {
93+
guard currentDepth <= 2 else { return [] }
94+
95+
var binFiles: [String] = []
96+
let contents = try FileManager.default.contentsOfDirectory(atPath: path)
97+
98+
for item in contents {
99+
guard !item.starts(with: ".") else { continue }
100+
101+
let itemPath = "\(path)/\(item)"
102+
var isDirectory: ObjCBool = false
103+
104+
guard FileManager.default.fileExists(atPath: itemPath, isDirectory: &isDirectory) else {
105+
continue
106+
}
107+
108+
if isDirectory.boolValue, currentDepth < 2 {
109+
// Recurse into subdirectory
110+
binFiles += try findBinFiles(in: itemPath, currentDepth: currentDepth + 1)
111+
} else if !isDirectory.boolValue, item.hasSuffix(".bin") {
112+
// Found a .bin file
113+
binFiles.append(itemPath)
114+
}
115+
}
116+
117+
return binFiles.sorted()
118+
}
119+
120+
let testFiles = try findBinFiles(in: basePath)
121+
122+
for testFilePath in testFiles {
123+
do {
124+
let testData = try Data(contentsOf: URL(fileURLWithPath: testFilePath))
125+
let rawTestCase = Testcase(description: URL(fileURLWithPath: testFilePath).lastPathComponent, data: testData)
126+
127+
let decoded = try JamTestnet.decodeTestcase(rawTestCase)
128+
allDecodedTestCases.append(decoded)
129+
logger.debug("Successfully loaded test case: \(testFilePath)")
130+
} catch {
131+
logger.warning("Failed to decode test case \(testFilePath): \(error)")
132+
}
133+
}
134+
135+
logger.info("Successfully loaded \(allDecodedTestCases.count) test cases")
136+
return allDecodedTestCases
137+
}
138+
}

0 commit comments

Comments
 (0)