github.com/prysmaticlabs/prysm@v1.4.4/validator/slashing-protection/local/standard-protection-format/round_trip_test.go (about)

     1  package interchangeformat_test
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"testing"
     9  
    10  	types "github.com/prysmaticlabs/eth2-types"
    11  	"github.com/prysmaticlabs/prysm/shared/testutil/assert"
    12  	"github.com/prysmaticlabs/prysm/shared/testutil/require"
    13  	"github.com/prysmaticlabs/prysm/validator/db/kv"
    14  	dbtest "github.com/prysmaticlabs/prysm/validator/db/testing"
    15  	protectionFormat "github.com/prysmaticlabs/prysm/validator/slashing-protection/local/standard-protection-format"
    16  	"github.com/prysmaticlabs/prysm/validator/slashing-protection/local/standard-protection-format/format"
    17  	slashtest "github.com/prysmaticlabs/prysm/validator/testing"
    18  )
    19  
    20  func TestImportExport_RoundTrip(t *testing.T) {
    21  	ctx := context.Background()
    22  	numValidators := 10
    23  	publicKeys, err := slashtest.CreateRandomPubKeys(numValidators)
    24  	require.NoError(t, err)
    25  	validatorDB := dbtest.SetupDB(t, publicKeys)
    26  
    27  	// First we setup some mock attesting and proposal histories and create a mock
    28  	// standard slashing protection format JSON struct.
    29  	attestingHistory, proposalHistory := slashtest.MockAttestingAndProposalHistories(publicKeys)
    30  	require.NoError(t, err)
    31  	wanted, err := slashtest.MockSlashingProtectionJSON(publicKeys, attestingHistory, proposalHistory)
    32  	require.NoError(t, err)
    33  
    34  	// We encode the standard slashing protection struct into a JSON format.
    35  	blob, err := json.Marshal(wanted)
    36  	require.NoError(t, err)
    37  	buf := bytes.NewBuffer(blob)
    38  
    39  	// Next, we attempt to import it into our validator database.
    40  	err = protectionFormat.ImportStandardProtectionJSON(ctx, validatorDB, buf)
    41  	require.NoError(t, err)
    42  
    43  	// Next up, we export our slashing protection database into the EIP standard file.
    44  	// Next, we attempt to import it into our validator database.
    45  	eipStandard, err := protectionFormat.ExportStandardProtectionJSON(ctx, validatorDB)
    46  	require.NoError(t, err)
    47  
    48  	// We compare the metadata fields from import to export.
    49  	require.Equal(t, wanted.Metadata, eipStandard.Metadata)
    50  
    51  	// The values in the data field of the EIP struct are not guaranteed to be sorted,
    52  	// so we create a map to verify we have the data we expected.
    53  	require.Equal(t, len(wanted.Data), len(eipStandard.Data))
    54  
    55  	dataByPubKey := make(map[string]*format.ProtectionData)
    56  	for _, item := range wanted.Data {
    57  		dataByPubKey[item.Pubkey] = item
    58  	}
    59  	for _, item := range eipStandard.Data {
    60  		want, ok := dataByPubKey[item.Pubkey]
    61  		require.Equal(t, true, ok)
    62  		require.Equal(t, len(want.SignedAttestations), len(item.SignedAttestations))
    63  		require.Equal(t, len(want.SignedBlocks), len(item.SignedBlocks))
    64  		wantedAttsByRoot := make(map[string]*format.SignedAttestation)
    65  		for _, att := range want.SignedAttestations {
    66  			wantedAttsByRoot[att.SigningRoot] = att
    67  		}
    68  		for _, att := range item.SignedAttestations {
    69  			wantedAtt, ok := wantedAttsByRoot[att.SigningRoot]
    70  			require.Equal(t, true, ok)
    71  			require.DeepEqual(t, wantedAtt, att)
    72  		}
    73  		require.DeepEqual(t, want.SignedBlocks, item.SignedBlocks)
    74  	}
    75  }
    76  
    77  func TestImportExport_RoundTrip_SkippedAttestationEpochs(t *testing.T) {
    78  	ctx := context.Background()
    79  	numValidators := 1
    80  	pubKeys, err := slashtest.CreateRandomPubKeys(numValidators)
    81  	require.NoError(t, err)
    82  	validatorDB := dbtest.SetupDB(t, pubKeys)
    83  	wanted := &format.EIPSlashingProtectionFormat{
    84  		Metadata: struct {
    85  			InterchangeFormatVersion string `json:"interchange_format_version"`
    86  			GenesisValidatorsRoot    string `json:"genesis_validators_root"`
    87  		}{
    88  			InterchangeFormatVersion: format.InterchangeFormatVersion,
    89  			GenesisValidatorsRoot:    fmt.Sprintf("%#x", [32]byte{}),
    90  		},
    91  		Data: []*format.ProtectionData{
    92  			{
    93  				Pubkey: fmt.Sprintf("%#x", pubKeys[0]),
    94  				SignedAttestations: []*format.SignedAttestation{
    95  					{
    96  						SourceEpoch: "1",
    97  						TargetEpoch: "2",
    98  					},
    99  					{
   100  						SourceEpoch: "8",
   101  						TargetEpoch: "9",
   102  					},
   103  				},
   104  				SignedBlocks: make([]*format.SignedBlock, 0),
   105  			},
   106  		},
   107  	}
   108  	// We encode the standard slashing protection struct into a JSON format.
   109  	blob, err := json.Marshal(wanted)
   110  	require.NoError(t, err)
   111  	buf := bytes.NewBuffer(blob)
   112  
   113  	// Next, we attempt to import it into our validator database.
   114  	err = protectionFormat.ImportStandardProtectionJSON(ctx, validatorDB, buf)
   115  	require.NoError(t, err)
   116  
   117  	// Next up, we export our slashing protection database into the EIP standard file.
   118  	// Next, we attempt to import it into our validator database.
   119  	eipStandard, err := protectionFormat.ExportStandardProtectionJSON(ctx, validatorDB)
   120  	require.NoError(t, err)
   121  
   122  	// We compare the metadata fields from import to export.
   123  	require.Equal(t, wanted.Metadata, eipStandard.Metadata)
   124  
   125  	// The values in the data field of the EIP struct are not guaranteed to be sorted,
   126  	// so we create a map to verify we have the data we expected.
   127  	require.Equal(t, len(wanted.Data), len(eipStandard.Data))
   128  	require.DeepEqual(t, wanted.Data, eipStandard.Data)
   129  }
   130  
   131  func TestImportInterchangeData_OK(t *testing.T) {
   132  	ctx := context.Background()
   133  	numValidators := 10
   134  	publicKeys, err := slashtest.CreateRandomPubKeys(numValidators)
   135  	require.NoError(t, err)
   136  	validatorDB := dbtest.SetupDB(t, publicKeys)
   137  
   138  	// First we setup some mock attesting and proposal histories and create a mock
   139  	// standard slashing protection format JSON struct.
   140  	attestingHistory, proposalHistory := slashtest.MockAttestingAndProposalHistories(publicKeys)
   141  	require.NoError(t, err)
   142  	standardProtectionFormat, err := slashtest.MockSlashingProtectionJSON(publicKeys, attestingHistory, proposalHistory)
   143  	require.NoError(t, err)
   144  
   145  	// We encode the standard slashing protection struct into a JSON format.
   146  	blob, err := json.Marshal(standardProtectionFormat)
   147  	require.NoError(t, err)
   148  	buf := bytes.NewBuffer(blob)
   149  
   150  	// Next, we attempt to import it into our validator database.
   151  	err = protectionFormat.ImportStandardProtectionJSON(ctx, validatorDB, buf)
   152  	require.NoError(t, err)
   153  
   154  	// Next, we attempt to retrieve the attesting and proposals histories from our database and
   155  	// verify those indeed match the originally generated mock histories.
   156  	for i := 0; i < len(publicKeys); i++ {
   157  		receivedAttestingHistory, err := validatorDB.AttestationHistoryForPubKey(ctx, publicKeys[i])
   158  		require.NoError(t, err)
   159  
   160  		wantedAttsByRoot := make(map[[32]byte]*kv.AttestationRecord)
   161  		for _, att := range attestingHistory[i] {
   162  			wantedAttsByRoot[att.SigningRoot] = att
   163  		}
   164  		for _, att := range receivedAttestingHistory {
   165  			wantedAtt, ok := wantedAttsByRoot[att.SigningRoot]
   166  			require.Equal(t, true, ok)
   167  			require.DeepEqual(t, wantedAtt, att)
   168  		}
   169  
   170  		proposals := proposalHistory[i].Proposals
   171  		receivedProposalHistory, err := validatorDB.ProposalHistoryForPubKey(ctx, publicKeys[i])
   172  		require.NoError(t, err)
   173  		rootsBySlot := make(map[types.Slot][]byte)
   174  		for _, proposal := range receivedProposalHistory {
   175  			rootsBySlot[proposal.Slot] = proposal.SigningRoot
   176  		}
   177  		for _, proposal := range proposals {
   178  			receivedRoot, ok := rootsBySlot[proposal.Slot]
   179  			require.DeepEqual(t, true, ok)
   180  			require.DeepEqual(
   181  				t,
   182  				receivedRoot,
   183  				proposal.SigningRoot,
   184  				"Imported proposals are different then the generated ones",
   185  			)
   186  		}
   187  	}
   188  }
   189  
   190  func TestImportInterchangeData_OK_SavesBlacklistedPublicKeys(t *testing.T) {
   191  	ctx := context.Background()
   192  	numValidators := 3
   193  	publicKeys, err := slashtest.CreateRandomPubKeys(numValidators)
   194  	require.NoError(t, err)
   195  	validatorDB := dbtest.SetupDB(t, publicKeys)
   196  
   197  	// First we setup some mock attesting and proposal histories and create a mock
   198  	// standard slashing protection format JSON struct.
   199  	attestingHistory, proposalHistory := slashtest.MockAttestingAndProposalHistories(publicKeys)
   200  	require.NoError(t, err)
   201  
   202  	standardProtectionFormat, err := slashtest.MockSlashingProtectionJSON(publicKeys, attestingHistory, proposalHistory)
   203  	require.NoError(t, err)
   204  
   205  	// We add a slashable block for public key at index 1.
   206  	pubKey0 := standardProtectionFormat.Data[0].Pubkey
   207  	standardProtectionFormat.Data[0].SignedBlocks = append(
   208  		standardProtectionFormat.Data[0].SignedBlocks,
   209  		&format.SignedBlock{
   210  			Slot:        "700",
   211  			SigningRoot: fmt.Sprintf("%#x", [32]byte{1}),
   212  		},
   213  		&format.SignedBlock{
   214  			Slot:        "700",
   215  			SigningRoot: fmt.Sprintf("%#x", [32]byte{2}),
   216  		},
   217  	)
   218  
   219  	// We add a slashable attestation for public key at index 1
   220  	// representing a double vote event.
   221  	pubKey1 := standardProtectionFormat.Data[1].Pubkey
   222  	standardProtectionFormat.Data[1].SignedAttestations = append(
   223  		standardProtectionFormat.Data[1].SignedAttestations,
   224  		&format.SignedAttestation{
   225  			TargetEpoch: "700",
   226  			SourceEpoch: "699",
   227  			SigningRoot: fmt.Sprintf("%#x", [32]byte{1}),
   228  		},
   229  		&format.SignedAttestation{
   230  			TargetEpoch: "700",
   231  			SourceEpoch: "699",
   232  			SigningRoot: fmt.Sprintf("%#x", [32]byte{2}),
   233  		},
   234  	)
   235  
   236  	// We add a slashable attestation for public key at index 2
   237  	// representing a surround vote event.
   238  	pubKey2 := standardProtectionFormat.Data[2].Pubkey
   239  	standardProtectionFormat.Data[2].SignedAttestations = append(
   240  		standardProtectionFormat.Data[2].SignedAttestations,
   241  		&format.SignedAttestation{
   242  			TargetEpoch: "800",
   243  			SourceEpoch: "805",
   244  			SigningRoot: fmt.Sprintf("%#x", [32]byte{4}),
   245  		},
   246  		&format.SignedAttestation{
   247  			TargetEpoch: "801",
   248  			SourceEpoch: "804",
   249  			SigningRoot: fmt.Sprintf("%#x", [32]byte{5}),
   250  		},
   251  	)
   252  
   253  	// We encode the standard slashing protection struct into a JSON format.
   254  	blob, err := json.Marshal(standardProtectionFormat)
   255  	require.NoError(t, err)
   256  	buf := bytes.NewBuffer(blob)
   257  
   258  	// Next, we attempt to import it into our validator database.
   259  	err = protectionFormat.ImportStandardProtectionJSON(ctx, validatorDB, buf)
   260  	require.NoError(t, err)
   261  
   262  	// Assert the three slashable keys in the imported JSON were saved to the database.
   263  	sKeys, err := validatorDB.EIPImportBlacklistedPublicKeys(ctx)
   264  	require.NoError(t, err)
   265  	slashableKeys := make(map[string]bool)
   266  	for _, pubKey := range sKeys {
   267  		pkString := fmt.Sprintf("%#x", pubKey)
   268  		slashableKeys[pkString] = true
   269  	}
   270  	ok := slashableKeys[pubKey0]
   271  	assert.Equal(t, true, ok)
   272  	ok = slashableKeys[pubKey1]
   273  	assert.Equal(t, true, ok)
   274  	ok = slashableKeys[pubKey2]
   275  	assert.Equal(t, true, ok)
   276  }
   277  
   278  func TestStore_ImportInterchangeData_BadFormat_PreventsDBWrites(t *testing.T) {
   279  	ctx := context.Background()
   280  	numValidators := 5
   281  	publicKeys, err := slashtest.CreateRandomPubKeys(numValidators)
   282  	require.NoError(t, err)
   283  	validatorDB := dbtest.SetupDB(t, publicKeys)
   284  
   285  	// First we setup some mock attesting and proposal histories and create a mock
   286  	// standard slashing protection format JSON struct.
   287  	attestingHistory, proposalHistory := slashtest.MockAttestingAndProposalHistories(publicKeys)
   288  	require.NoError(t, err)
   289  	standardProtectionFormat, err := slashtest.MockSlashingProtectionJSON(publicKeys, attestingHistory, proposalHistory)
   290  	require.NoError(t, err)
   291  
   292  	// We replace a slot of one of the blocks with junk data.
   293  	standardProtectionFormat.Data[0].SignedBlocks[0].Slot = "BadSlot"
   294  
   295  	// We encode the standard slashing protection struct into a JSON format.
   296  	blob, err := json.Marshal(standardProtectionFormat)
   297  	require.NoError(t, err)
   298  	buf := bytes.NewBuffer(blob)
   299  
   300  	// Next, we attempt to import it into our validator database and check that
   301  	// we obtain an error during the import process.
   302  	err = protectionFormat.ImportStandardProtectionJSON(ctx, validatorDB, buf)
   303  	assert.NotNil(t, err)
   304  
   305  	// Next, we attempt to retrieve the attesting and proposals histories from our database and
   306  	// verify nothing was saved to the DB. If there is an error in the import process, we need to make
   307  	// sure writing is an atomic operation: either the import succeeds and saves the slashing protection
   308  	// data to our DB, or it does not.
   309  	for i := 0; i < len(publicKeys); i++ {
   310  		receivedAttestingHistory, err := validatorDB.AttestationHistoryForPubKey(ctx, publicKeys[i])
   311  		require.NoError(t, err)
   312  		require.Equal(
   313  			t,
   314  			0,
   315  			len(receivedAttestingHistory),
   316  			"Imported attestation protection history is different than the empty default",
   317  		)
   318  		receivedHistory, err := validatorDB.ProposalHistoryForPubKey(ctx, publicKeys[i])
   319  		require.NoError(t, err)
   320  		require.DeepEqual(
   321  			t,
   322  			make([]*kv.Proposal, 0),
   323  			receivedHistory,
   324  			"Imported proposal signing root is different than the empty default",
   325  		)
   326  	}
   327  }