github.com/prysmaticlabs/prysm@v1.4.4/shared/aggregation/attestations/maxcover.go (about) 1 package attestations 2 3 import ( 4 "sort" 5 6 "github.com/pkg/errors" 7 "github.com/prysmaticlabs/go-bitfield" 8 ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" 9 "github.com/prysmaticlabs/prysm/shared/aggregation" 10 "github.com/prysmaticlabs/prysm/shared/bls" 11 "github.com/prysmaticlabs/prysm/shared/copyutil" 12 ) 13 14 // MaxCoverAttestationAggregation relies on Maximum Coverage greedy algorithm for aggregation. 15 // Aggregation occurs in many rounds, up until no more aggregation is possible (all attestations 16 // are overlapping). 17 func MaxCoverAttestationAggregation(atts []*ethpb.Attestation) ([]*ethpb.Attestation, error) { 18 if len(atts) < 2 { 19 return atts, nil 20 } 21 22 aggregated := attList(make([]*ethpb.Attestation, 0, len(atts))) 23 unaggregated := attList(atts) 24 25 if err := unaggregated.validate(); err != nil { 26 return nil, err 27 } 28 29 // Aggregation over n/2 rounds is enough to find all aggregatable items (exits earlier if there 30 // are many items that can be aggregated). 31 for i := 0; i < len(atts)/2; i++ { 32 if len(unaggregated) < 2 { 33 break 34 } 35 36 // Find maximum non-overlapping coverage. 37 maxCover := NewMaxCover(unaggregated) 38 solution, err := maxCover.Cover(len(atts), false /* allowOverlaps */) 39 if err != nil { 40 return aggregated.merge(unaggregated), err 41 } 42 43 // Exit earlier, if possible cover does not allow aggregation (less than two items). 44 if len(solution.Keys) < 2 { 45 break 46 } 47 48 // Create aggregated attestation and update solution lists. 49 if has, err := aggregated.hasCoverage(solution.Coverage); err != nil { 50 return nil, err 51 } else if !has { 52 att, err := unaggregated.selectUsingKeys(solution.Keys).aggregate(solution.Coverage) 53 if err != nil { 54 return aggregated.merge(unaggregated), err 55 } 56 aggregated = append(aggregated, att) 57 } 58 unaggregated = unaggregated.selectComplementUsingKeys(solution.Keys) 59 } 60 61 filtered, err := unaggregated.filterContained() 62 if err != nil { 63 return nil, err 64 } 65 return aggregated.merge(filtered), nil 66 } 67 68 // optMaxCoverAttestationAggregation relies on Maximum Coverage greedy algorithm for aggregation. 69 // Aggregation occurs in many rounds, up until no more aggregation is possible (all attestations 70 // are overlapping). 71 // NB: this method will replace the MaxCoverAttestationAggregation() above (and will be renamed to it). 72 // See https://hackmd.io/@farazdagi/in-place-attagg for design and rationale. 73 func optMaxCoverAttestationAggregation(atts []*ethpb.Attestation) ([]*ethpb.Attestation, error) { 74 if len(atts) < 2 { 75 return atts, nil 76 } 77 78 if err := attList(atts).validate(); err != nil { 79 return nil, err 80 } 81 82 // In the future this conversion will be redundant, as attestation bitlist will be of a Bitlist64 83 // type, so incoming `atts` parameters can be used as candidates list directly. 84 candidates := make([]*bitfield.Bitlist64, len(atts)) 85 for i := 0; i < len(atts); i++ { 86 var err error 87 candidates[i], err = atts[i].AggregationBits.ToBitlist64() 88 if err != nil { 89 return nil, err 90 } 91 } 92 coveredBitsSoFar := bitfield.NewBitlist64(candidates[0].Len()) 93 94 // In order not to re-allocate anything we rely on the very same underlying array, which 95 // can only shrink (while the `aggregated` slice length can increase). 96 // The `aggregated` slice grows by combining individual attestations and appending to that slice. 97 // Both aggregated and non-aggregated slices operate on the very same underlying array. 98 aggregated := atts[:0] 99 unaggregated := atts 100 101 // Aggregation over n/2 rounds is enough to find all aggregatable items (exits earlier if there 102 // are many items that can be aggregated). 103 for i := 0; i < len(atts)/2; i++ { 104 if len(unaggregated) < 2 { 105 break 106 } 107 108 // Find maximum non-overlapping coverage for subset of still non-processed candidates. 109 roundCandidates := candidates[len(aggregated) : len(aggregated)+len(unaggregated)] 110 selectedKeys, coverage, err := aggregation.MaxCover( 111 roundCandidates, len(roundCandidates), false /* allowOverlaps */) 112 if err != nil { 113 // Return aggregated attestations, and attestations that couldn't be aggregated. 114 return append(aggregated, unaggregated...), err 115 } 116 117 // Exit earlier, if possible cover does not allow aggregation (less than two items). 118 if selectedKeys.Count() < 2 { 119 break 120 } 121 122 // Pad selected key indexes, as `roundCandidates` is a subset of `candidates`. 123 keys := padSelectedKeys(selectedKeys.BitIndices(), len(aggregated)) 124 125 // Create aggregated attestation and update solution lists. Process aggregates only if they 126 // feature at least one unknown bit i.e. can increase the overall coverage. 127 xc, err := coveredBitsSoFar.XorCount(coverage) 128 if err != nil { 129 return nil, err 130 } 131 if xc > 0 { 132 aggIdx, err := aggregateAttestations(atts, keys, coverage) 133 if err != nil { 134 return append(aggregated, unaggregated...), err 135 } 136 137 // Unless we are already at the right position, swap aggregation and the first non-aggregated item. 138 idx0 := len(aggregated) 139 if idx0 < aggIdx { 140 atts[idx0], atts[aggIdx] = atts[aggIdx], atts[idx0] 141 candidates[idx0], candidates[aggIdx] = candidates[aggIdx], candidates[idx0] 142 } 143 144 // Expand to the newly created aggregate. 145 aggregated = atts[:idx0+1] 146 147 // Shift the starting point of the slice to the right. 148 unaggregated = unaggregated[1:] 149 150 // Update covered bits map. 151 if err := coveredBitsSoFar.NoAllocOr(coverage, coveredBitsSoFar); err != nil { 152 return nil, err 153 } 154 keys = keys[1:] 155 } 156 157 // Remove processed attestations. 158 rearrangeProcessedAttestations(atts, candidates, keys) 159 unaggregated = unaggregated[:len(unaggregated)-len(keys)] 160 } 161 162 filtered, err := attList(unaggregated).filterContained() 163 if err != nil { 164 return nil, err 165 } 166 return append(aggregated, filtered...), nil 167 } 168 169 // NewMaxCover returns initialized Maximum Coverage problem for attestations aggregation. 170 func NewMaxCover(atts []*ethpb.Attestation) *aggregation.MaxCoverProblem { 171 candidates := make([]*aggregation.MaxCoverCandidate, len(atts)) 172 for i := 0; i < len(atts); i++ { 173 candidates[i] = aggregation.NewMaxCoverCandidate(i, &atts[i].AggregationBits) 174 } 175 return &aggregation.MaxCoverProblem{Candidates: candidates} 176 } 177 178 // aggregate returns list as an aggregated attestation. 179 func (al attList) aggregate(coverage bitfield.Bitlist) (*ethpb.Attestation, error) { 180 if len(al) < 2 { 181 return nil, errors.Wrap(ErrInvalidAttestationCount, "cannot aggregate") 182 } 183 signs := make([]bls.Signature, len(al)) 184 for i := 0; i < len(al); i++ { 185 sig, err := signatureFromBytes(al[i].Signature) 186 if err != nil { 187 return nil, err 188 } 189 signs[i] = sig 190 } 191 return ðpb.Attestation{ 192 AggregationBits: coverage, 193 Data: copyutil.CopyAttestationData(al[0].Data), 194 Signature: aggregateSignatures(signs).Marshal(), 195 }, nil 196 } 197 198 // padSelectedKeys adds additional value to every key. 199 func padSelectedKeys(keys []int, pad int) []int { 200 for i, key := range keys { 201 keys[i] = key + pad 202 } 203 return keys 204 } 205 206 // aggregateAttestations combines signatures of selected attestations into a single aggregate attestation, and 207 // pushes that aggregated attestation into the position of the first of selected attestations. 208 func aggregateAttestations(atts []*ethpb.Attestation, keys []int, coverage *bitfield.Bitlist64) (targetIdx int, err error) { 209 if len(keys) < 2 || atts == nil || len(atts) < 2 { 210 return targetIdx, errors.Wrap(ErrInvalidAttestationCount, "cannot aggregate") 211 } 212 if coverage == nil || coverage.Count() == 0 { 213 return targetIdx, errors.New("invalid or empty coverage") 214 } 215 216 var data *ethpb.AttestationData 217 signs := make([]bls.Signature, 0, len(keys)) 218 for i, idx := range keys { 219 sig, err := signatureFromBytes(atts[idx].Signature) 220 if err != nil { 221 return targetIdx, err 222 } 223 signs = append(signs, sig) 224 if i == 0 { 225 data = copyutil.CopyAttestationData(atts[idx].Data) 226 targetIdx = idx 227 } 228 } 229 // Put aggregated attestation at a position of the first selected attestation. 230 atts[targetIdx] = ðpb.Attestation{ 231 // Append size byte, which will be unnecessary on switch to Bitlist64. 232 AggregationBits: coverage.ToBitlist(), 233 Data: data, 234 Signature: aggregateSignatures(signs).Marshal(), 235 } 236 return 237 } 238 239 // rearrangeProcessedAttestations pushes processed attestations to the end of the slice, returning 240 // the number of items re-arranged (so that caller can cut the slice, and allow processed items to be 241 // garbage collected). 242 func rearrangeProcessedAttestations(atts []*ethpb.Attestation, candidates []*bitfield.Bitlist64, processedKeys []int) { 243 if atts == nil || candidates == nil || processedKeys == nil { 244 return 245 } 246 // Set all selected keys to nil. 247 for _, idx := range processedKeys { 248 atts[idx] = nil 249 candidates[idx] = nil 250 } 251 // Re-arrange nil items, move them to end of slice. 252 sort.Ints(processedKeys) 253 lastIdx := len(atts) - 1 254 for _, idx0 := range processedKeys { 255 // Make sure that nil items are swapped for non-nil items only. 256 for lastIdx > idx0 && atts[lastIdx] == nil { 257 lastIdx-- 258 } 259 if idx0 == lastIdx { 260 break 261 } 262 atts[idx0], atts[lastIdx] = atts[lastIdx], atts[idx0] 263 candidates[idx0], candidates[lastIdx] = candidates[lastIdx], candidates[idx0] 264 } 265 } 266 267 // merge combines two attestation lists into one. 268 func (al attList) merge(al1 attList) attList { 269 return append(al, al1...) 270 } 271 272 // selectUsingKeys returns only items with specified keys. 273 func (al attList) selectUsingKeys(keys []int) attList { 274 filtered := make([]*ethpb.Attestation, len(keys)) 275 for i, key := range keys { 276 filtered[i] = al[key] 277 } 278 return filtered 279 } 280 281 // selectComplementUsingKeys returns only items with keys that are NOT specified. 282 func (al attList) selectComplementUsingKeys(keys []int) attList { 283 foundInKeys := func(key int) bool { 284 for i := 0; i < len(keys); i++ { 285 if keys[i] == key { 286 keys[i] = keys[len(keys)-1] 287 keys = keys[:len(keys)-1] 288 return true 289 } 290 } 291 return false 292 } 293 filtered := al[:0] 294 for i, att := range al { 295 if !foundInKeys(i) { 296 filtered = append(filtered, att) 297 } 298 } 299 return filtered 300 } 301 302 // hasCoverage returns true if a given coverage is found in attestations list. 303 func (al attList) hasCoverage(coverage bitfield.Bitlist) (bool, error) { 304 for _, att := range al { 305 x, err := att.AggregationBits.Xor(coverage) 306 if err != nil { 307 return false, err 308 } 309 if x.Count() == 0 { 310 return true, nil 311 } 312 } 313 return false, nil 314 } 315 316 // filterContained removes attestations that are contained within other attestations. 317 func (al attList) filterContained() (attList, error) { 318 if len(al) < 2 { 319 return al, nil 320 } 321 sort.Slice(al, func(i, j int) bool { 322 return al[i].AggregationBits.Count() > al[j].AggregationBits.Count() 323 }) 324 filtered := al[:0] 325 filtered = append(filtered, al[0]) 326 for i := 1; i < len(al); i++ { 327 c, err := filtered[len(filtered)-1].AggregationBits.Contains(al[i].AggregationBits) 328 if err != nil { 329 return nil, err 330 } 331 if c { 332 continue 333 } 334 filtered = append(filtered, al[i]) 335 } 336 return filtered, nil 337 } 338 339 // validate checks attestation list for validity (equal bitlength, non-nil bitlist etc). 340 func (al attList) validate() error { 341 if al == nil { 342 return errors.New("nil list") 343 } 344 if len(al) == 0 { 345 return errors.Wrap(aggregation.ErrInvalidMaxCoverProblem, "empty list") 346 } 347 if al[0].AggregationBits == nil || al[0].AggregationBits.Len() == 0 { 348 return errors.Wrap(aggregation.ErrInvalidMaxCoverProblem, "bitlist cannot be nil or empty") 349 } 350 for i := 1; i < len(al); i++ { 351 if al[i].AggregationBits == nil || al[i].AggregationBits.Len() == 0 { 352 return errors.Wrap(aggregation.ErrInvalidMaxCoverProblem, "bitlist cannot be nil or empty") 353 } 354 } 355 return nil 356 }