github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations_test.go (about)

     1  package validator
     2  
     3  import (
     4  	"bytes"
     5  	"sort"
     6  	"testing"
     7  
     8  	types "github.com/prysmaticlabs/eth2-types"
     9  	"github.com/prysmaticlabs/go-bitfield"
    10  	ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    11  	"github.com/prysmaticlabs/prysm/shared/featureconfig"
    12  	"github.com/prysmaticlabs/prysm/shared/testutil"
    13  	"github.com/prysmaticlabs/prysm/shared/testutil/assert"
    14  	"github.com/prysmaticlabs/prysm/shared/testutil/require"
    15  )
    16  
    17  func TestProposer_ProposerAtts_sortByProfitability(t *testing.T) {
    18  	atts := proposerAtts([]*ethpb.Attestation{
    19  		testutil.HydrateAttestation(&ethpb.Attestation{Data: &ethpb.AttestationData{Slot: 4}, AggregationBits: bitfield.Bitlist{0b11100000}}),
    20  		testutil.HydrateAttestation(&ethpb.Attestation{Data: &ethpb.AttestationData{Slot: 1}, AggregationBits: bitfield.Bitlist{0b11000000}}),
    21  		testutil.HydrateAttestation(&ethpb.Attestation{Data: &ethpb.AttestationData{Slot: 2}, AggregationBits: bitfield.Bitlist{0b11100000}}),
    22  		testutil.HydrateAttestation(&ethpb.Attestation{Data: &ethpb.AttestationData{Slot: 4}, AggregationBits: bitfield.Bitlist{0b11110000}}),
    23  		testutil.HydrateAttestation(&ethpb.Attestation{Data: &ethpb.AttestationData{Slot: 1}, AggregationBits: bitfield.Bitlist{0b11100000}}),
    24  		testutil.HydrateAttestation(&ethpb.Attestation{Data: &ethpb.AttestationData{Slot: 3}, AggregationBits: bitfield.Bitlist{0b11000000}}),
    25  	})
    26  	want := proposerAtts([]*ethpb.Attestation{
    27  		testutil.HydrateAttestation(&ethpb.Attestation{Data: &ethpb.AttestationData{Slot: 4}, AggregationBits: bitfield.Bitlist{0b11110000}}),
    28  		testutil.HydrateAttestation(&ethpb.Attestation{Data: &ethpb.AttestationData{Slot: 4}, AggregationBits: bitfield.Bitlist{0b11100000}}),
    29  		testutil.HydrateAttestation(&ethpb.Attestation{Data: &ethpb.AttestationData{Slot: 3}, AggregationBits: bitfield.Bitlist{0b11000000}}),
    30  		testutil.HydrateAttestation(&ethpb.Attestation{Data: &ethpb.AttestationData{Slot: 2}, AggregationBits: bitfield.Bitlist{0b11100000}}),
    31  		testutil.HydrateAttestation(&ethpb.Attestation{Data: &ethpb.AttestationData{Slot: 1}, AggregationBits: bitfield.Bitlist{0b11100000}}),
    32  		testutil.HydrateAttestation(&ethpb.Attestation{Data: &ethpb.AttestationData{Slot: 1}, AggregationBits: bitfield.Bitlist{0b11000000}}),
    33  	})
    34  	atts, err := atts.sortByProfitability()
    35  	if err != nil {
    36  		t.Error(err)
    37  	}
    38  	require.DeepEqual(t, want, atts)
    39  }
    40  
    41  func TestProposer_ProposerAtts_sortByProfitabilityUsingMaxCover(t *testing.T) {
    42  	resetCfg := featureconfig.InitWithReset(&featureconfig.Flags{
    43  		ProposerAttsSelectionUsingMaxCover: true,
    44  	})
    45  	defer resetCfg()
    46  
    47  	type testData struct {
    48  		slot types.Slot
    49  		bits bitfield.Bitlist
    50  	}
    51  	getAtts := func(data []testData) proposerAtts {
    52  		var atts proposerAtts
    53  		for _, att := range data {
    54  			atts = append(atts, testutil.HydrateAttestation(&ethpb.Attestation{
    55  				Data: &ethpb.AttestationData{Slot: att.slot}, AggregationBits: att.bits}))
    56  		}
    57  		return atts
    58  	}
    59  
    60  	t.Run("no atts", func(t *testing.T) {
    61  		atts := getAtts([]testData{})
    62  		want := getAtts([]testData{})
    63  		atts, err := atts.sortByProfitability()
    64  		if err != nil {
    65  			t.Error(err)
    66  		}
    67  		require.DeepEqual(t, want, atts)
    68  	})
    69  
    70  	t.Run("single att", func(t *testing.T) {
    71  		atts := getAtts([]testData{
    72  			{4, bitfield.Bitlist{0b11100000, 0b1}},
    73  		})
    74  		want := getAtts([]testData{
    75  			{4, bitfield.Bitlist{0b11100000, 0b1}},
    76  		})
    77  		atts, err := atts.sortByProfitability()
    78  		if err != nil {
    79  			t.Error(err)
    80  		}
    81  		require.DeepEqual(t, want, atts)
    82  	})
    83  
    84  	t.Run("single att per slot", func(t *testing.T) {
    85  		atts := getAtts([]testData{
    86  			{1, bitfield.Bitlist{0b11000000, 0b1}},
    87  			{4, bitfield.Bitlist{0b11100000, 0b1}},
    88  		})
    89  		want := getAtts([]testData{
    90  			{4, bitfield.Bitlist{0b11100000, 0b1}},
    91  			{1, bitfield.Bitlist{0b11000000, 0b1}},
    92  		})
    93  		atts, err := atts.sortByProfitability()
    94  		if err != nil {
    95  			t.Error(err)
    96  		}
    97  		require.DeepEqual(t, want, atts)
    98  	})
    99  
   100  	t.Run("two atts on one of the slots", func(t *testing.T) {
   101  		atts := getAtts([]testData{
   102  			{1, bitfield.Bitlist{0b11000000, 0b1}},
   103  			{4, bitfield.Bitlist{0b11100000, 0b1}},
   104  			{4, bitfield.Bitlist{0b11110000, 0b1}},
   105  		})
   106  		want := getAtts([]testData{
   107  			{4, bitfield.Bitlist{0b11110000, 0b1}},
   108  			{4, bitfield.Bitlist{0b11100000, 0b1}},
   109  			{1, bitfield.Bitlist{0b11000000, 0b1}},
   110  		})
   111  		atts, err := atts.sortByProfitability()
   112  		if err != nil {
   113  			t.Error(err)
   114  		}
   115  		require.DeepEqual(t, want, atts)
   116  	})
   117  
   118  	t.Run("compare to native sort", func(t *testing.T) {
   119  		// The naive sort will end up with 0b11001000 being selected second (which is not optimal
   120  		// as it only contains a single unknown bit).
   121  		// The max-cover based approach will select 0b00001100 instead, despite lower bit count
   122  		// (since it has two new/unknown bits).
   123  		t.Run("naive", func(t *testing.T) {
   124  			resetCfg := featureconfig.InitWithReset(&featureconfig.Flags{
   125  				ProposerAttsSelectionUsingMaxCover: false,
   126  			})
   127  			defer resetCfg()
   128  
   129  			atts := getAtts([]testData{
   130  				{1, bitfield.Bitlist{0b11000011, 0b1}},
   131  				{1, bitfield.Bitlist{0b11001000, 0b1}},
   132  				{1, bitfield.Bitlist{0b00001100, 0b1}},
   133  			})
   134  			want := getAtts([]testData{
   135  				{1, bitfield.Bitlist{0b11000011, 0b1}},
   136  				{1, bitfield.Bitlist{0b11001000, 0b1}},
   137  				{1, bitfield.Bitlist{0b00001100, 0b1}},
   138  			})
   139  			atts, err := atts.sortByProfitability()
   140  			if err != nil {
   141  				t.Error(err)
   142  			}
   143  			require.DeepEqual(t, want, atts)
   144  		})
   145  		t.Run("max-cover", func(t *testing.T) {
   146  			resetCfg := featureconfig.InitWithReset(&featureconfig.Flags{
   147  				ProposerAttsSelectionUsingMaxCover: true,
   148  			})
   149  			defer resetCfg()
   150  
   151  			atts := getAtts([]testData{
   152  				{1, bitfield.Bitlist{0b11000011, 0b1}},
   153  				{1, bitfield.Bitlist{0b11001000, 0b1}},
   154  				{1, bitfield.Bitlist{0b00001100, 0b1}},
   155  			})
   156  			want := getAtts([]testData{
   157  				{1, bitfield.Bitlist{0b11000011, 0b1}},
   158  				{1, bitfield.Bitlist{0b00001100, 0b1}},
   159  				{1, bitfield.Bitlist{0b11001000, 0b1}},
   160  			})
   161  			atts, err := atts.sortByProfitability()
   162  			if err != nil {
   163  				t.Error(err)
   164  			}
   165  			require.DeepEqual(t, want, atts)
   166  		})
   167  	})
   168  
   169  	t.Run("multiple slots", func(t *testing.T) {
   170  		atts := getAtts([]testData{
   171  			{2, bitfield.Bitlist{0b11100000, 0b1}},
   172  			{4, bitfield.Bitlist{0b11100000, 0b1}},
   173  			{1, bitfield.Bitlist{0b11000000, 0b1}},
   174  			{4, bitfield.Bitlist{0b11110000, 0b1}},
   175  			{1, bitfield.Bitlist{0b11100000, 0b1}},
   176  			{3, bitfield.Bitlist{0b11000000, 0b1}},
   177  		})
   178  		want := getAtts([]testData{
   179  			{4, bitfield.Bitlist{0b11110000, 0b1}},
   180  			{4, bitfield.Bitlist{0b11100000, 0b1}},
   181  			{3, bitfield.Bitlist{0b11000000, 0b1}},
   182  			{2, bitfield.Bitlist{0b11100000, 0b1}},
   183  			{1, bitfield.Bitlist{0b11100000, 0b1}},
   184  			{1, bitfield.Bitlist{0b11000000, 0b1}},
   185  		})
   186  		atts, err := atts.sortByProfitability()
   187  		if err != nil {
   188  			t.Error(err)
   189  		}
   190  		require.DeepEqual(t, want, atts)
   191  	})
   192  
   193  	t.Run("selected and non selected atts sorted by bit count", func(t *testing.T) {
   194  		// Items at slot 4, must be first split into two lists by max-cover, with
   195  		// 0b10000011 scoring higher (as it provides more info in addition to already selected
   196  		// attestations) than 0b11100001 (despite naive bit count suggesting otherwise). Then,
   197  		// both selected and non-selected attestations must be additionally sorted by bit count.
   198  		atts := getAtts([]testData{
   199  			{4, bitfield.Bitlist{0b00000001, 0b1}},
   200  			{4, bitfield.Bitlist{0b11100001, 0b1}},
   201  			{1, bitfield.Bitlist{0b11000000, 0b1}},
   202  			{2, bitfield.Bitlist{0b11100000, 0b1}},
   203  			{4, bitfield.Bitlist{0b10000011, 0b1}},
   204  			{4, bitfield.Bitlist{0b11111000, 0b1}},
   205  			{1, bitfield.Bitlist{0b11100000, 0b1}},
   206  			{3, bitfield.Bitlist{0b11000000, 0b1}},
   207  		})
   208  		want := getAtts([]testData{
   209  			{4, bitfield.Bitlist{0b11111000, 0b1}},
   210  			{4, bitfield.Bitlist{0b10000011, 0b1}},
   211  			{4, bitfield.Bitlist{0b11100001, 0b1}},
   212  			{4, bitfield.Bitlist{0b00000001, 0b1}},
   213  			{3, bitfield.Bitlist{0b11000000, 0b1}},
   214  			{2, bitfield.Bitlist{0b11100000, 0b1}},
   215  			{1, bitfield.Bitlist{0b11100000, 0b1}},
   216  			{1, bitfield.Bitlist{0b11000000, 0b1}},
   217  		})
   218  		atts, err := atts.sortByProfitability()
   219  		if err != nil {
   220  			t.Error(err)
   221  		}
   222  		require.DeepEqual(t, want, atts)
   223  	})
   224  }
   225  
   226  func TestProposer_ProposerAtts_dedup(t *testing.T) {
   227  	data1 := testutil.HydrateAttestationData(&ethpb.AttestationData{
   228  		Slot: 4,
   229  	})
   230  	data2 := testutil.HydrateAttestationData(&ethpb.AttestationData{
   231  		Slot: 5,
   232  	})
   233  	tests := []struct {
   234  		name string
   235  		atts proposerAtts
   236  		want proposerAtts
   237  	}{
   238  		{
   239  			name: "nil list",
   240  			atts: nil,
   241  			want: proposerAtts(nil),
   242  		},
   243  		{
   244  			name: "empty list",
   245  			atts: proposerAtts{},
   246  			want: proposerAtts{},
   247  		},
   248  		{
   249  			name: "single item",
   250  			atts: proposerAtts{
   251  				&ethpb.Attestation{AggregationBits: bitfield.Bitlist{}},
   252  			},
   253  			want: proposerAtts{
   254  				&ethpb.Attestation{AggregationBits: bitfield.Bitlist{}},
   255  			},
   256  		},
   257  		{
   258  			name: "two items no duplicates",
   259  			atts: proposerAtts{
   260  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b10111110, 0x01}},
   261  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b01111111, 0x01}},
   262  			},
   263  			want: proposerAtts{
   264  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b01111111, 0x01}},
   265  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b10111110, 0x01}},
   266  			},
   267  		},
   268  		{
   269  			name: "two items with duplicates",
   270  			atts: proposerAtts{
   271  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0xba, 0x01}},
   272  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0xba, 0x01}},
   273  			},
   274  			want: proposerAtts{
   275  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0xba, 0x01}},
   276  			},
   277  		},
   278  		{
   279  			name: "sorted no duplicates",
   280  			atts: proposerAtts{
   281  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b11001111, 0b1}},
   282  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b01101101, 0b1}},
   283  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00101011, 0b1}},
   284  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b10100000, 0b1}},
   285  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00010000, 0b1}},
   286  			},
   287  			want: proposerAtts{
   288  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b11001111, 0b1}},
   289  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b01101101, 0b1}},
   290  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00101011, 0b1}},
   291  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b10100000, 0b1}},
   292  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00010000, 0b1}},
   293  			},
   294  		},
   295  		{
   296  			name: "sorted with duplicates",
   297  			atts: proposerAtts{
   298  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b11001111, 0b1}},
   299  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b01101101, 0b1}},
   300  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b01101101, 0b1}},
   301  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b01101101, 0b1}},
   302  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00001111, 0b1}},
   303  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000011, 0b1}},
   304  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000011, 0b1}},
   305  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000001, 0b1}},
   306  			},
   307  			want: proposerAtts{
   308  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b11001111, 0b1}},
   309  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b01101101, 0b1}},
   310  			},
   311  		},
   312  		{
   313  			name: "all equal",
   314  			atts: proposerAtts{
   315  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000011, 0b1}},
   316  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000011, 0b1}},
   317  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000011, 0b1}},
   318  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000011, 0b1}},
   319  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000011, 0b1}},
   320  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000011, 0b1}},
   321  			},
   322  			want: proposerAtts{
   323  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000011, 0b1}},
   324  			},
   325  		},
   326  		{
   327  			name: "unsorted no duplicates",
   328  			atts: proposerAtts{
   329  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b01101101, 0b1}},
   330  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00100010, 0b1}},
   331  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b10100101, 0b1}},
   332  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00010000, 0b1}},
   333  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b11001111, 0b1}},
   334  			},
   335  			want: proposerAtts{
   336  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b11001111, 0b1}},
   337  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b01101101, 0b1}},
   338  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b10100101, 0b1}},
   339  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00100010, 0b1}},
   340  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00010000, 0b1}},
   341  			},
   342  		},
   343  		{
   344  			name: "unsorted with duplicates",
   345  			atts: proposerAtts{
   346  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00001111, 0b1}},
   347  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b11001111, 0b1}},
   348  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b10100101, 0b1}},
   349  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b10100101, 0b1}},
   350  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000001, 0b1}},
   351  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000011, 0b1}},
   352  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b11001111, 0b1}},
   353  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b01101101, 0b1}},
   354  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000001, 0b1}},
   355  			},
   356  			want: proposerAtts{
   357  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b11001111, 0b1}},
   358  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b01101101, 0b1}},
   359  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b10100101, 0b1}},
   360  			},
   361  		},
   362  		{
   363  			name: "no proper subset (same root)",
   364  			atts: proposerAtts{
   365  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000101, 0b1}},
   366  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000011, 0b1}},
   367  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b10000001, 0b1}},
   368  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00011001, 0b1}},
   369  			},
   370  			want: proposerAtts{
   371  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00011001, 0b1}},
   372  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000011, 0b1}},
   373  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000101, 0b1}},
   374  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b10000001, 0b1}},
   375  			},
   376  		},
   377  		{
   378  			name: "proper subset (same root)",
   379  			atts: proposerAtts{
   380  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00001111, 0b1}},
   381  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b11001111, 0b1}},
   382  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00001111, 0b1}},
   383  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00001111, 0b1}},
   384  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000001, 0b1}},
   385  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000011, 0b1}},
   386  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b11001111, 0b1}},
   387  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000001, 0b1}},
   388  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b01101101, 0b1}},
   389  			},
   390  			want: proposerAtts{
   391  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b11001111, 0b1}},
   392  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b01101101, 0b1}},
   393  			},
   394  		},
   395  		{
   396  			name: "no proper subset (different root)",
   397  			atts: proposerAtts{
   398  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000101, 0b1}},
   399  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000011, 0b1}},
   400  				&ethpb.Attestation{Data: data2, AggregationBits: bitfield.Bitlist{0b10000001, 0b1}},
   401  				&ethpb.Attestation{Data: data2, AggregationBits: bitfield.Bitlist{0b00011001, 0b1}},
   402  			},
   403  			want: proposerAtts{
   404  				&ethpb.Attestation{Data: data2, AggregationBits: bitfield.Bitlist{0b00011001, 0b1}},
   405  				&ethpb.Attestation{Data: data2, AggregationBits: bitfield.Bitlist{0b10000001, 0b1}},
   406  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000011, 0b1}},
   407  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000101, 0b1}},
   408  			},
   409  		},
   410  		{
   411  			name: "proper subset (different root 1)",
   412  			atts: proposerAtts{
   413  				&ethpb.Attestation{Data: data2, AggregationBits: bitfield.Bitlist{0b00001111, 0b1}},
   414  				&ethpb.Attestation{Data: data2, AggregationBits: bitfield.Bitlist{0b11001111, 0b1}},
   415  				&ethpb.Attestation{Data: data2, AggregationBits: bitfield.Bitlist{0b00001111, 0b1}},
   416  				&ethpb.Attestation{Data: data2, AggregationBits: bitfield.Bitlist{0b00001111, 0b1}},
   417  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000001, 0b1}},
   418  				&ethpb.Attestation{Data: data2, AggregationBits: bitfield.Bitlist{0b00000011, 0b1}},
   419  				&ethpb.Attestation{Data: data2, AggregationBits: bitfield.Bitlist{0b11001111, 0b1}},
   420  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00000001, 0b1}},
   421  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b01101101, 0b1}},
   422  			},
   423  			want: proposerAtts{
   424  				&ethpb.Attestation{Data: data2, AggregationBits: bitfield.Bitlist{0b11001111, 0b1}},
   425  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b01101101, 0b1}},
   426  			},
   427  		},
   428  		{
   429  			name: "proper subset (different root 2)",
   430  			atts: proposerAtts{
   431  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b00001111, 0b1}},
   432  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b11001111, 0b1}},
   433  				&ethpb.Attestation{Data: data2, AggregationBits: bitfield.Bitlist{0b00001111, 0b1}},
   434  				&ethpb.Attestation{Data: data2, AggregationBits: bitfield.Bitlist{0b11001111, 0b1}},
   435  			},
   436  			want: proposerAtts{
   437  				&ethpb.Attestation{Data: data2, AggregationBits: bitfield.Bitlist{0b11001111, 0b1}},
   438  				&ethpb.Attestation{Data: data1, AggregationBits: bitfield.Bitlist{0b11001111, 0b1}},
   439  			},
   440  		},
   441  	}
   442  	for _, tt := range tests {
   443  		t.Run(tt.name, func(t *testing.T) {
   444  			atts, err := tt.atts.dedup()
   445  			if err != nil {
   446  				t.Error(err)
   447  			}
   448  			sort.Slice(atts, func(i, j int) bool {
   449  				if atts[i].AggregationBits.Count() == atts[j].AggregationBits.Count() {
   450  					if atts[i].Data.Slot == atts[j].Data.Slot {
   451  						return bytes.Compare(atts[i].AggregationBits, atts[j].AggregationBits) <= 0
   452  					}
   453  					return atts[i].Data.Slot > atts[j].Data.Slot
   454  				}
   455  				return atts[i].AggregationBits.Count() > atts[j].AggregationBits.Count()
   456  			})
   457  			assert.DeepEqual(t, tt.want, atts)
   458  		})
   459  	}
   460  }