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 }