-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathprocess_sync_committee.zig
More file actions
231 lines (204 loc) · 9.98 KB
/
process_sync_committee.zig
File metadata and controls
231 lines (204 loc) · 9.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
const std = @import("std");
const Allocator = std.mem.Allocator;
const ForkSeq = @import("config").ForkSeq;
const BeaconConfig = @import("config").BeaconConfig;
const EpochCache = @import("../cache/epoch_cache.zig").EpochCache;
const BeaconState = @import("fork_types").BeaconState;
const ValidatorIndex = types.primitive.ValidatorIndex.Type;
const AggregatedSignatureSet = @import("../utils/signature_sets.zig").AggregatedSignatureSet;
const types = @import("consensus_types");
const SyncAggregate = types.altair.SyncAggregate.Type;
const preset = @import("preset").preset;
const Root = types.primitive.Root.Type;
const G2_POINT_AT_INFINITY = @import("constants").G2_POINT_AT_INFINITY;
const c = @import("constants");
const bls = @import("bls");
const computeSigningRoot = @import("../utils/signing_root.zig").computeSigningRoot;
const verifyAggregatedSignatureSet = @import("../utils/signature_sets.zig").verifyAggregatedSignatureSet;
const getBeaconProposer = @import("../cache/get_beacon_proposer.zig").getBeaconProposer;
const balance_utils = @import("../utils/balance.zig");
const getBlockRootAtSlot = @import("../utils/block_root.zig").getBlockRootAtSlot;
const Node = @import("persistent_merkle_tree").Node;
const increaseBalance = balance_utils.increaseBalance;
const decreaseBalance = balance_utils.decreaseBalance;
pub fn processSyncAggregate(
comptime fork: ForkSeq,
allocator: Allocator,
config: *const BeaconConfig,
epoch_cache: *const EpochCache,
state: *BeaconState(fork),
sync_aggregate: *const SyncAggregate,
verify_signatures: bool,
) !void {
const committee_indices = @as(*const [preset.SYNC_COMMITTEE_SIZE]ValidatorIndex, @ptrCast(epoch_cache.current_sync_committee_indexed.get().getValidatorIndices()));
const sync_committee_bits = sync_aggregate.sync_committee_bits;
const signature = sync_aggregate.sync_committee_signature;
// different from the spec but not sure how to get through signature verification for default/empty SyncAggregate in the spec test
if (verify_signatures) {
const participant_indices = try sync_committee_bits.intersectValues(
ValidatorIndex,
allocator,
committee_indices,
);
defer participant_indices.deinit();
// When there's no participation we cons ider the signature valid and just ignore it
if (participant_indices.items.len > 0) {
const previous_slot = @max(try state.slot(), 1) - 1;
const root_signed = try getBlockRootAtSlot(fork, state, previous_slot);
const domain = try config.getDomain(epoch_cache.epoch, c.DOMAIN_SYNC_COMMITTEE, previous_slot);
const pubkeys = try allocator.alloc(*const bls.PublicKey, participant_indices.items.len);
defer allocator.free(pubkeys);
for (0..participant_indices.items.len) |i| {
pubkeys[i] = &epoch_cache.index_to_pubkey.items[participant_indices.items[i]];
}
var signing_root: Root = undefined;
try computeSigningRoot(types.primitive.Root, root_signed, domain, &signing_root);
const signature_set = AggregatedSignatureSet{
.pubkeys = pubkeys,
.signing_root = signing_root,
.signature = signature,
};
if (!try verifyAggregatedSignatureSet(&signature_set)) {
return error.SyncCommitteeSignatureInvalid;
}
} else {
if (!std.mem.eql(u8, &signature, &c.G2_POINT_AT_INFINITY)) {
return error.EmptySyncCommitteeSignatureIsNotInfinity;
}
}
}
const sync_participant_reward = epoch_cache.sync_participant_reward;
const sync_proposer_reward = epoch_cache.sync_proposer_reward;
const proposer_index = try getBeaconProposer(fork, epoch_cache, state, try state.slot());
var balances = try state.balances();
var proposer_balance = try balances.get(proposer_index);
for (0..preset.SYNC_COMMITTEE_SIZE) |i| {
const index = committee_indices[i];
if (try sync_committee_bits.get(i)) {
// Positive rewards for participants
if (index == proposer_index) {
proposer_balance += sync_participant_reward;
} else {
try increaseBalance(fork, state, index, sync_participant_reward);
}
// Proposer reward
proposer_balance += sync_proposer_reward;
// TODO: proposer_rewards inside state
} else {
// Negative rewards for non participants
if (index == proposer_index) {
proposer_balance = @max(0, proposer_balance - sync_participant_reward);
} else {
try decreaseBalance(fork, state, index, sync_participant_reward);
}
}
}
// Apply proposer balance
try balances.set(proposer_index, proposer_balance);
}
/// Consumers should deinit the returned pubkeys
/// this is to be used when we implement getBlockSignatureSets
/// see https://github.com/ChainSafe/state-transition-z/issues/72
pub fn getSyncCommitteeSignatureSet(
allocator: Allocator,
config: *const BeaconConfig,
epoch_cache: *const EpochCache,
sync_aggregate: *const SyncAggregate,
block_slot: u64,
block_parent_root: *const Root,
participant_indices: ?[]usize,
) !?AggregatedSignatureSet {
const signature = sync_aggregate.sync_committee_signature;
const participant_indices_ = if (participant_indices) |pi| pi else blk: {
const committee_indices = @as(*const [preset.SYNC_COMMITTEE_SIZE]u64, @ptrCast(epoch_cache.current_sync_committee_indexed.get().getValidatorIndices()));
break :blk (try sync_aggregate.sync_committee_bits.intersectValues(ValidatorIndex, allocator, committee_indices)).items;
};
// When there's no participation we consider the signature valid and just ignore it
if (participant_indices_.len == 0) {
// Must set signature as G2_POINT_AT_INFINITY when participating bits are empty
// https://github.com/ethereum/eth2.0-specs/blob/30f2a076377264677e27324a8c3c78c590ae5e20/specs/altair/bls.md#eth2_fast_aggregate_verify
if (std.mem.eql(u8, &signature, &G2_POINT_AT_INFINITY)) {
return null;
}
return error.EmptySyncCommitteeSignatureIsNotInfinity;
}
// The spec uses the state to get the previous slot
// ```python
// previous_slot = max(state.slot, Slot(1)) - Slot(1)
// ```
// However we need to run the function getSyncCommitteeSignatureSet() for all the blocks in a epoch
// with the same state when verifying blocks in batch on RangeSync. Therefore we use the block.slot.
const previous_slot = block_slot -| 1;
// The spec uses the state to get the root at previousSlot
// ```python
// get_block_root_at_slot(state, previous_slot)
// ```
// However we need to run the function getSyncCommitteeSignatureSet() for all the blocks in a epoch
// with the same state when verifying blocks in batch on RangeSync.
//
// On skipped slots state block roots just copy the latest block, so using the parentRoot here is equivalent.
// So getSyncCommitteeSignatureSet() can be called with a state in any slot (with the correct shuffling)
const root_signed = block_parent_root;
const domain = try config.getDomain(epoch_cache.epoch, c.DOMAIN_SYNC_COMMITTEE, previous_slot);
const pubkeys = try allocator.alloc(*const bls.PublicKey, participant_indices_.len);
for (0..participant_indices_.len) |i| {
pubkeys[i] = &epoch_cache.index_to_pubkey.items[participant_indices_[i]];
}
var signing_root: Root = undefined;
try computeSigningRoot(types.primitive.Root, &root_signed, domain, &signing_root);
return .{
.pubkeys = pubkeys,
.signing_root = signing_root,
.signature = signature,
};
}
const TestCachedBeaconState = @import("../test_utils/root.zig").TestCachedBeaconState;
const test_utils = @import("../test_utils/root.zig");
test "process sync aggregate - sanity" {
const allocator = std.testing.allocator;
const pool_size = 256 * 5;
var pool = try Node.Pool.init(allocator, pool_size);
defer pool.deinit();
var test_state = try TestCachedBeaconState.init(allocator, &pool, 256);
defer test_state.deinit();
const state = test_state.cached_state.state;
const config = test_state.cached_state.config;
const epoch_cache = test_state.cached_state.epoch_cache;
const fork_state = state.castToFork(.electra);
const previous_slot = try state.slot() - 1;
const root_signed = try getBlockRootAtSlot(.electra, fork_state, previous_slot);
const domain = try config.getDomain(epoch_cache.epoch, c.DOMAIN_SYNC_COMMITTEE, previous_slot);
var signing_root: Root = undefined;
try computeSigningRoot(types.primitive.Root, root_signed, domain, &signing_root);
const committee_indices = @as(*const [preset.SYNC_COMMITTEE_SIZE]ValidatorIndex, @ptrCast(epoch_cache.current_sync_committee_indexed.get().getValidatorIndices()));
// validator 0 signs
const sig0 = try test_utils.interopSign(committee_indices[0], &signing_root);
// validator 1 signs
const sig1 = try test_utils.interopSign(committee_indices[1], &signing_root);
const agg_sig = try bls.AggregateSignature.aggregate(&.{ &sig0, &sig1 }, true);
var sync_aggregate: types.electra.SyncAggregate.Type = types.electra.SyncAggregate.default_value;
sync_aggregate.sync_committee_signature = agg_sig.toSignature().compress();
try sync_aggregate.sync_committee_bits.set(0, true);
// don't set bit 1 yet
const res = processSyncAggregate(
.electra,
allocator,
config,
epoch_cache,
fork_state,
&sync_aggregate,
true,
);
try std.testing.expect(res == error.SyncCommitteeSignatureInvalid);
// now set bit 1
try sync_aggregate.sync_committee_bits.set(1, true);
try processSyncAggregate(
.electra,
allocator,
config,
epoch_cache,
fork_state,
&sync_aggregate,
true,
);
}