Skip to content

Commit af446d5

Browse files
authored
fuzz tests debug fix (#351)
* new tests and fix * add tests * add new test * fix * remove error not used * disable wrong tests * fix * remove extra check * fix tests * don't stop after fuzzer stop * try fix * fix assurance validation
1 parent 27ec741 commit af446d5

Some content is hidden

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

64 files changed

+2934
-3118
lines changed

Blockchain/Sources/Blockchain/RuntimeProtocols/Assurances.swift

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,29 @@ public protocol Assurances {
2323
}
2424

2525
extension Assurances {
26+
public func validateAssurances(
27+
extrinsics: ExtrinsicAvailability,
28+
parentHash: Data32
29+
) throws {
30+
for assurance in extrinsics.assurances {
31+
guard assurance.parentHash == parentHash else {
32+
throw AssurancesError.invalidAssuranceParentHash
33+
}
34+
35+
let hash = Blake2b256.hash(assurance.parentHash, assurance.assurance)
36+
let payload = SigningContext.available + hash.data
37+
let validatorKey = try currentValidators.at(Int(assurance.validatorIndex))
38+
let pubkey = try Ed25519.PublicKey(from: validatorKey.ed25519)
39+
guard pubkey.verify(signature: assurance.signature, message: payload) else {
40+
throw AssurancesError.invalidAssuranceSignature
41+
}
42+
}
43+
}
44+
2645
public func update(
2746
config: ProtocolConfigRef,
2847
timeslot: TimeslotIndex,
2948
extrinsic: ExtrinsicAvailability,
30-
parentHash: Data32
3149
) throws -> (
3250
newReports: ConfigFixedSizeArray<
3351
ReportItem?,
@@ -45,20 +63,6 @@ extension Assurances {
4563
}
4664
}
4765

48-
for assurance in extrinsic.assurances {
49-
guard assurance.parentHash == parentHash else {
50-
throw AssurancesError.invalidAssuranceParentHash
51-
}
52-
53-
let hash = Blake2b256.hash(assurance.parentHash, assurance.assurance)
54-
let payload = SigningContext.available + hash.data
55-
let validatorKey = try currentValidators.at(Int(assurance.validatorIndex))
56-
let pubkey = try Ed25519.PublicKey(from: validatorKey.ed25519)
57-
guard pubkey.verify(signature: assurance.signature, message: payload) else {
58-
throw AssurancesError.invalidAssuranceSignature
59-
}
60-
}
61-
6266
var availabilityCount = Array(repeating: 0, count: config.value.totalNumberOfCores)
6367
for assurance in extrinsic.assurances {
6468
for (coreIdx, bit) in assurance.assurance.enumerated() where bit {

Blockchain/Sources/Blockchain/RuntimeProtocols/Guaranteeing.swift

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,36 @@ extension Guaranteeing {
8585
.map { StateKeys.ServiceAccountKey(index: $0.serviceIndex) }
8686
}
8787

88+
public func validateGuarantees(
89+
config: ProtocolConfigRef,
90+
extrinsic: ExtrinsicGuarantees
91+
) async throws(GuaranteeingError) {
92+
for guarantee in extrinsic.guarantees {
93+
var totalGasUsage = Gas(0)
94+
let report = guarantee.workReport
95+
96+
for digest in report.digests {
97+
guard let acc = try? await serviceAccount(index: digest.serviceIndex) else {
98+
throw .invalidServiceIndex
99+
}
100+
101+
guard acc.codeHash == digest.codeHash else {
102+
throw .invalidResultCodeHash
103+
}
104+
105+
guard digest.gasLimit >= acc.minAccumlateGas else {
106+
throw .invalidServiceGas
107+
}
108+
109+
totalGasUsage += digest.gasLimit
110+
}
111+
112+
guard totalGasUsage <= config.value.workReportAccumulationGas else {
113+
throw .outOfGas
114+
}
115+
}
116+
}
117+
88118
public func update(
89119
config: ProtocolConfigRef,
90120
timeslot: TimeslotIndex,
@@ -120,7 +150,6 @@ extension Guaranteeing {
120150
var reporters = Set<Ed25519PublicKey>()
121151

122152
for guarantee in extrinsic.guarantees {
123-
var totalGasUsage = Gas(0)
124153
let report = guarantee.workReport
125154

126155
guard guarantee.timeslot <= timeslot else {
@@ -162,26 +191,6 @@ extension Guaranteeing {
162191
guard coreAuthorizationPool[coreIndex].contains(report.authorizerHash) else {
163192
throw .invalidReportAuthorizer
164193
}
165-
166-
for digest in report.digests {
167-
guard let acc = try? await serviceAccount(index: digest.serviceIndex) else {
168-
throw .invalidServiceIndex
169-
}
170-
171-
guard acc.codeHash == digest.codeHash else {
172-
throw .invalidResultCodeHash
173-
}
174-
175-
guard digest.gasLimit >= acc.minAccumlateGas else {
176-
throw .invalidServiceGas
177-
}
178-
179-
totalGasUsage += digest.gasLimit
180-
}
181-
182-
guard totalGasUsage <= config.value.workReportAccumulationGas else {
183-
throw .outOfGas
184-
}
185194
}
186195

187196
let recentWorkPackageHashes: Set<Data32> = Set(recentHistory.items.flatMap(\.lookup.keys))

Blockchain/Sources/Blockchain/RuntimeProtocols/Preimages.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,6 @@ extension Preimages {
5050
let preimages = preimages.preimages
5151
var updates: [PreimageUpdate] = []
5252

53-
guard preimages.isSortedAndUnique() else {
54-
throw PreimagesError.preimagesNotSorted
55-
}
56-
5753
for preimage in preimages {
5854
let hash = preimage.data.blake2b256hash()
5955

Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public final class Runtime {
1010
public enum Error: Swift.Error {
1111
case safroleError(SafroleError)
1212
case disputesError(DisputesError)
13-
case invalidTimeslot(got: TimeslotIndex, context: TimeslotIndex)
13+
case invalidTimeslot(got: TimeslotIndex, exp: TimeslotIndex)
1414
case authorizationError(AuthorizationError)
1515
case encodeError(any Swift.Error)
1616
case invalidExtrinsicHash
@@ -19,18 +19,13 @@ public final class Runtime {
1919
case invalidHeaderEpochMarker
2020
case invalidHeaderWinningTickets
2121
case invalidHeaderOffendersMarkers
22-
case invalidAssuranceParentHash
23-
case invalidAssuranceSignature
24-
case assuranceForEmptyCore
25-
case preimagesNotSorted
26-
case invalidPreimageServiceIndex
27-
case duplicatedPreimage
2822
case invalidAuthorTicket
2923
case invalidAuthorKey
3024
case invalidBlockSeal(any Swift.Error)
3125
case invalidVrfSignature
3226
case other(any Swift.Error)
3327
case validateError(any Swift.Error)
28+
case headerTimeslotTooSmall
3429
}
3530

3631
public struct ApplyContext {
@@ -65,7 +60,7 @@ public final class Runtime {
6560
}
6661

6762
guard block.header.timeslot <= context.timeslot else {
68-
throw Error.invalidTimeslot(got: block.header.timeslot, context: context.timeslot)
63+
throw Error.invalidTimeslot(got: block.header.timeslot, exp: context.timeslot)
6964
}
7065

7166
// epoch is validated at apply time by Safrole
@@ -126,27 +121,37 @@ public final class Runtime {
126121
}.mapError { _ in Error.invalidVrfSignature }.get()
127122
}
128123

129-
public func validate(block: Validated<BlockRef>, state: StateRef, context: ApplyContext) throws(Error) {
124+
public func validate(block: Validated<BlockRef>, state: StateRef, context: ApplyContext) async throws(Error) {
130125
try validateHeader(block: block, state: state, context: context)
131126

132127
let block = block.value
128+
let extrinsic = block.extrinsic
133129

134-
for ext in block.extrinsic.availability.assurances {
135-
guard ext.parentHash == block.header.parentHash else {
136-
throw Error.invalidAssuranceParentHash
137-
}
130+
// NOTE: extrinsic validations not related to header or state are not here, but in their own Validate impl
131+
do {
132+
// tickets
133+
try state.value.validateTickets(config: config, slot: block.header.timeslot, extrinsics: extrinsic.tickets)
134+
// assurances
135+
try state.value.validateAssurances(extrinsics: extrinsic.availability, parentHash: block.header.parentHash)
136+
// guarantees
137+
try await state.value.validateGuarantees(config: config, extrinsic: extrinsic.reports)
138+
} catch {
139+
throw Error.validateError(error)
138140
}
139-
140-
// TODO: abstract input validation logic from Safrole state update function and call it here
141-
// TODO: validate other things
142141
}
143142

144-
public func apply(block: BlockRef, state prevState: StateRef, context: ApplyContext) async throws(Error) -> StateRef {
143+
public func apply(block: BlockRef, state prevState: StateRef, context: ApplyContext? = nil) async throws(Error) -> StateRef {
145144
let validatedBlock = try Result(catching: { try block.toValidated(config: config) })
146145
.mapError(Error.validateError)
147146
.get()
148147

149-
try validate(block: validatedBlock, state: prevState, context: context)
148+
let prevStateRoot = await prevState.value.stateRoot
149+
150+
try await validate(
151+
block: validatedBlock,
152+
state: prevState,
153+
context: context ?? .init(timeslot: block.value.header.timeslot, stateRoot: prevStateRoot)
154+
)
150155

151156
return try await apply(block: validatedBlock, state: prevState)
152157
}
@@ -187,9 +192,9 @@ public final class Runtime {
187192

188193
newState.recentHistory.updatePartial(parentStateRoot: block.header.priorStateRoot)
189194

190-
let reporters = try await updateGuarantees(block: block, state: &newState)
195+
let reporters = try await updateReports(block: block, state: &newState)
191196

192-
// after reports as it need old recent history
197+
// after reports as reports need old recent history
193198
try updateRecentHistory(block: block, state: &newState, accumulateRoot: accumulateRoot)
194199

195200
// update authorization pool and queue
@@ -279,14 +284,13 @@ public final class Runtime {
279284
config: config,
280285
timeslot: block.header.timeslot,
281286
extrinsic: block.extrinsic.availability,
282-
parentHash: block.header.parentHash
283287
)
284288

285289
newState.reports = newReports
286290
return availableReports
287291
}
288292

289-
public func updateGuarantees(block: BlockRef, state newState: inout State) async throws -> [Ed25519PublicKey] {
293+
public func updateReports(block: BlockRef, state newState: inout State) async throws -> [Ed25519PublicKey] {
290294
let result = try await newState.update(
291295
config: config, timeslot: newState.timeslot, extrinsic: block.extrinsic.reports
292296
)

Blockchain/Sources/Blockchain/RuntimeProtocols/Safrole.swift

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,26 @@ func withoutOffenders(
203203
}
204204

205205
extension Safrole {
206+
public func validateTickets(
207+
config: ProtocolConfigRef,
208+
slot: TimeslotIndex,
209+
extrinsics: ExtrinsicTickets,
210+
) throws(SafroleError) {
211+
guard slot > timeslot else {
212+
throw .invalidTimeslot
213+
}
214+
215+
if (slot % UInt32(config.value.epochLength)) < UInt32(config.value.ticketSubmissionEndSlot) {
216+
guard extrinsics.tickets.count <= config.value.maxTicketsPerExtrinsic else {
217+
throw .tooManyExtrinsics
218+
}
219+
} else {
220+
guard extrinsics.tickets.isEmpty else {
221+
throw .extrinsicsNotAllowed
222+
}
223+
}
224+
}
225+
206226
public func updateSafrole(
207227
config: ProtocolConfigRef,
208228
slot: TimeslotIndex,
@@ -230,20 +250,6 @@ extension Safrole {
230250
let newPhase = slot % epochLength
231251
let isEpochChange = currentEpoch != newEpoch
232252

233-
guard slot > timeslot else {
234-
throw .invalidTimeslot
235-
}
236-
237-
if newPhase < ticketSubmissionEndSlot {
238-
guard extrinsics.tickets.count <= config.value.maxTicketsPerExtrinsic else {
239-
throw .tooManyExtrinsics
240-
}
241-
} else {
242-
guard extrinsics.tickets.isEmpty else {
243-
throw .extrinsicsNotAllowed
244-
}
245-
}
246-
247253
do {
248254
let ctx = try Bandersnatch.RingContext(size: UInt(config.value.totalNumberOfValidators))
249255
let commitment = try Bandersnatch.RingCommitment(data: ticketsVerifier)

Blockchain/Sources/Blockchain/Types/ExtrinsicPreimages.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,15 @@ extension ExtrinsicPreimages.PreimageItem: Comparable {
3636
return lhs.data.lexicographicallyPrecedes(rhs.data)
3737
}
3838
}
39+
40+
extension ExtrinsicPreimages: Validate {
41+
public enum Error: Swift.Error {
42+
case preimagesNotSorted
43+
}
44+
45+
public func validate(config _: Config) throws {
46+
guard preimages.isSortedAndUnique() else {
47+
throw Error.preimagesNotSorted
48+
}
49+
}
50+
}

Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,6 +1008,8 @@ public class Upgrade: HostCall {
10081008

10091009
let codeHash: Data32? = try? Data32(state.readMemory(address: regs[0], length: 32))
10101010

1011+
logger.debug("new codeHash: \(codeHash?.description ?? "nil")")
1012+
10111013
if let codeHash, var acc = try await x.state.accounts.value.get(serviceAccount: x.serviceIndex) {
10121014
acc.codeHash = codeHash
10131015
acc.minAccumlateGas = Gas(regs[1])

Blockchain/Sources/Blockchain/VMInvocations/Invocations/AccumulateInvocation.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public func accumulate(
3131
return .init(state: state, transfers: [], commitment: nil, gasUsed: Gas(0), provide: [])
3232
}
3333

34-
let initialIndex = try Blake2b256.hash(JamEncoder.encode(serviceIndex, state.entropy, timeslot)).data.decode(UInt32.self)
34+
let initialIndex = try Blake2b256.hash(JamEncoder.encode(UInt(serviceIndex), state.entropy, UInt(timeslot))).data.decode(UInt32.self)
3535
let nextAccountIndex = try await AccumulateContext.check(
3636
i: (initialIndex % serviceIndexModValue) + 256,
3737
accounts: state.accounts.toRef()

Fuzzing/Sources/Fuzzing/FuzzingClient.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ public class FuzzingClient {
120120
)
121121

122122
// import block locally
123-
currentStateRef = try await runtime.apply(block: blockRef.toValidated(config: config), state: currentStateRef!)
123+
currentStateRef = try await runtime.apply(block: blockRef, state: currentStateRef!)
124124
let currentStateRoot = await currentStateRef!.value.stateRoot
125125

126126
// import block on target

Fuzzing/Sources/Fuzzing/FuzzingTarget.swift

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,21 @@ public class FuzzingTarget {
3838
public func run() async throws {
3939
try socket.create()
4040

41-
let connection = try socket.acceptConnection()
41+
logger.info("Fuzzing target listening for connections")
4242

43-
try await handleFuzzer(connection: connection)
43+
while true {
44+
do {
45+
logger.info("Waiting for new connection")
46+
let connection = try socket.acceptConnection()
47+
48+
try await handleFuzzer(connection: connection)
4449

45-
connection.close()
46-
logger.info("Connection closed")
50+
connection.close()
51+
logger.info("Connection closed, waiting for next connection")
52+
} catch {
53+
logger.error("Error handling connection: \(error)")
54+
}
55+
}
4756
}
4857

4958
private func handleFuzzer(connection: FuzzingSocketConnection) async throws {
@@ -91,8 +100,7 @@ public class FuzzingTarget {
91100
do {
92101
guard let stateRef = currentStateRef else { throw FuzzingTargetError.stateNotSet }
93102

94-
let blockRef = try block.asRef().toValidated(config: config)
95-
let newStateRef = try await runtime.apply(block: blockRef, state: stateRef)
103+
let newStateRef = try await runtime.apply(block: block.asRef(), state: stateRef)
96104

97105
currentStateRef = newStateRef
98106

0 commit comments

Comments
 (0)