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 &ethpb.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] = &ethpb.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  }