Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 68 additions & 9 deletions pkgs/cli/src/test_driver.zig
Original file line number Diff line number Diff line change
Expand Up @@ -649,8 +649,19 @@ fn processBlockStep(
const parent_state_ptr = driver.state_map.get(block.parent_root) orelse
return error.UnknownParent;

const target_intervals = slotToIntervals(block.slot);
try advanceForkchoiceIntervals(driver, target_intervals, true);
const tick_to_slot = switch (step_obj.get("tickToSlot") orelse JsonValue{ .bool = true }) {
.bool => |b| b,
else => return error.InvalidField,
};
if (tick_to_slot) {
const target_intervals = slotToIntervals(block.slot);
try advanceForkchoiceIntervals(driver, target_intervals, true);
} else {
const current_slot = driver.fork_choice.fcStore.slot_clock.timeSlots.load(.monotonic);
if (block.slot > current_slot +| node_constants.MAX_FUTURE_SLOT_TOLERANCE) {
return forkchoice.ForkChoiceError.BlockTooFarInFuture;
}
}

const new_state_ptr = try driver.allocator.create(types.BeamState);
errdefer {
Expand Down Expand Up @@ -718,16 +729,23 @@ fn processTickStep(
// Tick step supports two alternative forms:
// "time": unix timestamp — convert to intervals via genesis_time
// "interval": direct interval count
const has_proposal = blk: {
const value = step_obj.get("hasProposal") orelse step_obj.get("has_proposal") orelse break :blk false;
break :blk switch (value) {
.bool => |b| b,
else => false,
};
};
const anchor_genesis_time = driver.fork_choice.anchorState.config.genesis_time;

if (step_obj.get("time")) |tv| {
const time_value = try parseNonNegativeU64(tv);
if (time_value < anchor_genesis_time) return; // tick before genesis is a no-op
const target_intervals = timeToIntervals(anchor_genesis_time, time_value);
try advanceForkchoiceIntervals(driver, target_intervals, false);
try advanceForkchoiceIntervals(driver, target_intervals, has_proposal);
} else if (step_obj.get("interval")) |iv| {
const target_interval = try parseNonNegativeU64(iv);
try advanceForkchoiceIntervals(driver, target_interval, false);
try advanceForkchoiceIntervals(driver, target_interval, has_proposal);
} else {
return error.MissingField; // neither time nor interval
}
Expand Down Expand Up @@ -847,16 +865,29 @@ fn processGossipAggregatedAttestationStep(
}
}

// Register as aggregated payload in fork-choice
driver.fork_choice.storeAggregatedPayload(&att_data, proof_template, false, .block_payload) catch |err| {
var indices = types.aggregationBitsToValidatorIndices(&aggregation_bits, driver.allocator) catch
return error.InvalidField;
defer indices.deinit(driver.allocator);

if (indices.items.len == 0) {
return error.EmptyAggregationBits;
}
const validators_slice = driver.fork_choice.anchorState.validators.constSlice();
for (indices.items) |vi| {
if (vi >= validators_slice.len) {
return error.InvalidValidatorId;
}
}
if (stepExpectsSignatureOrProofFailure(step_obj)) {
return error.SignatureVerificationNotSupported;
}

driver.fork_choice.storeAggregatedPayload(&att_data, proof_template, false, .gossip) catch |err| {
std.debug.print("test_driver: gossipAggregatedAttestation storeAggregatedPayload failed: {s}\n", .{@errorName(err)});
return err;
};

// Also register individual attestations
var indices = types.aggregationBitsToValidatorIndices(&aggregation_bits, driver.allocator) catch
return error.InvalidField;
defer indices.deinit(driver.allocator);
for (indices.items) |vi| {
const att = types.Attestation{
.validator_id = @intCast(vi),
Expand All @@ -868,6 +899,34 @@ fn processGossipAggregatedAttestationStep(
_ = driver.fork_choice.updateHead() catch {};
}

fn stepExpectsSignatureOrProofFailure(step_obj: std.json.ObjectMap) bool {
if (step_obj.get("rejectionReason")) |reason_value| {
if (reason_value == .string) {
const reason = reason_value.string;
if (std.mem.eql(u8, reason, "INVALID_SIGNATURE") or
std.mem.eql(u8, reason, "INVALID_BLOCK_PROOF"))
{
return true;
}
}
}

if (step_obj.get("expectedError")) |err_value| {
if (err_value == .string) {
const msg = err_value.string;
if (std.mem.indexOf(u8, msg, "Signature") != null or
std.mem.indexOf(u8, msg, "signature") != null or
std.mem.indexOf(u8, msg, "proof") != null or
std.mem.indexOf(u8, msg, "Proof") != null)
{
return true;
}
}
}

return false;
}

// ---------------------------------------------------------------------------
// Step dispatch & response building
// ---------------------------------------------------------------------------
Expand Down
3 changes: 2 additions & 1 deletion pkgs/node/src/forkchoice.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3484,7 +3484,8 @@ pub const ForkChoice = struct {
// missing root is never derived and the fetch is never enqueued.
if (is_gossip) {
const now_intervals = self.fcStore.slot_clock.time.load(.monotonic);
const attestation_start_interval = data.slot * constants.INTERVALS_PER_SLOT;
const attestation_start_interval = std.math.mul(u64, data.slot, constants.INTERVALS_PER_SLOT) catch
return GossipAttestationValidationError.AttestationTooFarInFuture;
if (attestation_start_interval > now_intervals + constants.GOSSIP_DISPARITY_INTERVALS) {
return GossipAttestationValidationError.AttestationTooFarInFuture;
}
Expand Down
Loading