github.com/ewagmig/fabric@v2.1.1+incompatible/core/common/validation/statebased/validator_keylevel_test.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package statebased 8 9 import ( 10 "fmt" 11 "testing" 12 13 "github.com/hyperledger/fabric-protos-go/common" 14 pb "github.com/hyperledger/fabric-protos-go/peer" 15 "github.com/hyperledger/fabric/common/errors" 16 "github.com/hyperledger/fabric/core/ledger" 17 "github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/rwsetutil" 18 "github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/version" 19 "github.com/hyperledger/fabric/protoutil" 20 "github.com/stretchr/testify/assert" 21 ) 22 23 type mockPolicyEvaluator struct { 24 EvaluateRV error 25 EvaluateResByPolicy map[string]error 26 } 27 28 func (m *mockPolicyEvaluator) Evaluate(policyBytes []byte, signatureSet []*protoutil.SignedData) error { 29 if res, ok := m.EvaluateResByPolicy[string(policyBytes)]; ok { 30 return res 31 } 32 33 return m.EvaluateRV 34 } 35 36 func buildBlockWithTxs(txs ...[]byte) *common.Block { 37 return &common.Block{ 38 Header: &common.BlockHeader{ 39 Number: 1, 40 }, 41 Data: &common.BlockData{ 42 Data: txs, 43 }, 44 } 45 } 46 47 func buildTXWithRwset(rws []byte) []byte { 48 return protoutil.MarshalOrPanic(&common.Envelope{ 49 Payload: protoutil.MarshalOrPanic( 50 &common.Payload{ 51 Data: protoutil.MarshalOrPanic( 52 &pb.Transaction{ 53 Actions: []*pb.TransactionAction{ 54 { 55 Payload: protoutil.MarshalOrPanic(&pb.ChaincodeActionPayload{ 56 Action: &pb.ChaincodeEndorsedAction{ 57 ProposalResponsePayload: protoutil.MarshalOrPanic( 58 &pb.ProposalResponsePayload{ 59 Extension: protoutil.MarshalOrPanic(&pb.ChaincodeAction{Results: rws}), 60 }, 61 ), 62 }, 63 }), 64 }, 65 }, 66 }, 67 ), 68 }, 69 ), 70 }) 71 } 72 73 func rwsetBytes(t *testing.T, cc string) []byte { 74 rwsb := rwsetutil.NewRWSetBuilder() 75 rwsb.AddToWriteSet(cc, "key", []byte("value")) 76 rws := rwsb.GetTxReadWriteSet() 77 rwsetbytes, err := rws.ToProtoBytes() 78 assert.NoError(t, err) 79 80 return rwsetbytes 81 } 82 83 func TestKeylevelValidation(t *testing.T) { 84 t.Parallel() 85 86 // Scenario: we validate a transaction that writes 87 // to a key that contains key-level validation params. 88 // We simulate policy check success and failure 89 90 vpMetadataKey := pb.MetaDataKeys_VALIDATION_PARAMETER.String() 91 mr := &mockState{GetStateMetadataRv: map[string][]byte{vpMetadataKey: []byte("EP")}, GetPrivateDataMetadataByHashRv: map[string][]byte{vpMetadataKey: []byte("EP")}} 92 ms := &mockStateFetcher{FetchStateRv: mr} 93 pm := &KeyLevelValidationParameterManagerImpl{PolicyTranslator: &mockTranslator{}, StateFetcher: ms} 94 pe := &mockPolicyEvaluator{} 95 validator := NewKeyLevelValidator(NewV13Evaluator(pe, pm), pm) 96 97 rwsb := rwsetBytes(t, "cc") 98 prp := []byte("barf") 99 block := buildBlockWithTxs(buildTXWithRwset(rwsetUpdatingMetadataFor("cc", "key")), buildTXWithRwset(rwsetUpdatingMetadataFor("cc", "key"))) 100 101 validator.PreValidate(1, block) 102 103 endorsements := []*pb.Endorsement{ 104 { 105 Signature: []byte("signature"), 106 Endorser: []byte("endorser"), 107 }, 108 } 109 110 go func() { 111 validator.PostValidate("cc", 1, 0, fmt.Errorf("")) 112 }() 113 114 err := validator.Validate("cc", 1, 1, rwsb, prp, []byte("CCEP"), endorsements) 115 assert.NoError(t, err) 116 117 pe.EvaluateRV = fmt.Errorf("policy evaluation error") 118 119 err = validator.Validate("cc", 1, 1, rwsb, prp, []byte("CCEP"), endorsements) 120 assert.Error(t, err) 121 assert.IsType(t, &errors.VSCCEndorsementPolicyError{}, err) 122 } 123 124 func TestKeylevelValidationPvtData(t *testing.T) { 125 t.Parallel() 126 127 // Scenario: we validate a transaction that writes 128 // to a pvt key that contains key-level validation params. 129 // We simulate policy check success and failure 130 131 vpMetadataKey := pb.MetaDataKeys_VALIDATION_PARAMETER.String() 132 mr := &mockState{GetStateMetadataRv: map[string][]byte{vpMetadataKey: []byte("EP")}, GetPrivateDataMetadataByHashRv: map[string][]byte{vpMetadataKey: []byte("EP")}} 133 ms := &mockStateFetcher{FetchStateRv: mr} 134 pm := &KeyLevelValidationParameterManagerImpl{PolicyTranslator: &mockTranslator{}, StateFetcher: ms} 135 pe := &mockPolicyEvaluator{} 136 validator := NewKeyLevelValidator(NewV13Evaluator(pe, pm), pm) 137 138 rwsbu := rwsetutil.NewRWSetBuilder() 139 rwsbu.AddToPvtAndHashedWriteSet("cc", "coll", "key", []byte("value")) 140 rws := rwsbu.GetTxReadWriteSet() 141 rwsb, err := rws.ToProtoBytes() 142 assert.NoError(t, err) 143 prp := []byte("barf") 144 block := buildBlockWithTxs(buildTXWithRwset(rwsetUpdatingMetadataFor("cc", "key")), buildTXWithRwset(rwsetUpdatingMetadataFor("cc", "key"))) 145 146 validator.PreValidate(1, block) 147 148 go func() { 149 validator.PostValidate("cc", 1, 0, fmt.Errorf("")) 150 }() 151 152 err = validator.Validate("cc", 1, 1, rwsb, prp, []byte("CCEP"), []*pb.Endorsement{}) 153 assert.NoError(t, err) 154 155 pe.EvaluateRV = fmt.Errorf("policy evaluation error") 156 157 err = validator.Validate("cc", 1, 1, rwsb, prp, []byte("CCEP"), []*pb.Endorsement{}) 158 assert.Error(t, err) 159 assert.IsType(t, &errors.VSCCEndorsementPolicyError{}, err) 160 } 161 162 func TestKeylevelValidationMetaUpdate(t *testing.T) { 163 t.Parallel() 164 165 // Scenario: we validate a transaction that updates 166 // the key-level validation parameters for a key. 167 // We simulate policy check success and failure 168 169 vpMetadataKey := pb.MetaDataKeys_VALIDATION_PARAMETER.String() 170 mr := &mockState{GetStateMetadataRv: map[string][]byte{vpMetadataKey: []byte("EP")}, GetPrivateDataMetadataByHashRv: map[string][]byte{vpMetadataKey: []byte("EP")}} 171 ms := &mockStateFetcher{FetchStateRv: mr} 172 pm := &KeyLevelValidationParameterManagerImpl{PolicyTranslator: &mockTranslator{}, StateFetcher: ms} 173 pe := &mockPolicyEvaluator{} 174 validator := NewKeyLevelValidator(NewV13Evaluator(pe, pm), pm) 175 176 rwsbu := rwsetutil.NewRWSetBuilder() 177 rwsbu.AddToMetadataWriteSet("cc", "key", map[string][]byte{}) 178 rws := rwsbu.GetTxReadWriteSet() 179 rwsb, err := rws.ToProtoBytes() 180 assert.NoError(t, err) 181 prp := []byte("barf") 182 block := buildBlockWithTxs(buildTXWithRwset(rwsetUpdatingMetadataFor("cc", "key")), buildTXWithRwset(rwsetUpdatingMetadataFor("cc", "key"))) 183 184 validator.PreValidate(1, block) 185 186 go func() { 187 validator.PostValidate("cc", 1, 0, fmt.Errorf("")) 188 }() 189 190 err = validator.Validate("cc", 1, 1, rwsb, prp, []byte("CCEP"), []*pb.Endorsement{}) 191 assert.NoError(t, err) 192 193 pe.EvaluateRV = fmt.Errorf("policy evaluation error") 194 195 err = validator.Validate("cc", 1, 1, rwsb, prp, []byte("CCEP"), []*pb.Endorsement{}) 196 assert.Error(t, err) 197 assert.IsType(t, &errors.VSCCEndorsementPolicyError{}, err) 198 } 199 200 func TestKeylevelValidationPvtMetaUpdate(t *testing.T) { 201 t.Parallel() 202 203 // Scenario: we validate a transaction that updates 204 // the key-level validation parameters for a pvt key. 205 // We simulate policy check success and failure 206 207 vpMetadataKey := pb.MetaDataKeys_VALIDATION_PARAMETER.String() 208 mr := &mockState{GetStateMetadataRv: map[string][]byte{vpMetadataKey: []byte("EP")}, GetPrivateDataMetadataByHashRv: map[string][]byte{vpMetadataKey: []byte("EP")}} 209 ms := &mockStateFetcher{FetchStateRv: mr} 210 pm := &KeyLevelValidationParameterManagerImpl{PolicyTranslator: &mockTranslator{}, StateFetcher: ms} 211 pe := &mockPolicyEvaluator{} 212 validator := NewKeyLevelValidator(NewV13Evaluator(pe, pm), pm) 213 214 rwsbu := rwsetutil.NewRWSetBuilder() 215 rwsbu.AddToHashedMetadataWriteSet("cc", "coll", "key", map[string][]byte{}) 216 rws := rwsbu.GetTxReadWriteSet() 217 rwsb, err := rws.ToProtoBytes() 218 assert.NoError(t, err) 219 prp := []byte("barf") 220 block := buildBlockWithTxs(buildTXWithRwset(rwsetUpdatingMetadataFor("cc", "key")), buildTXWithRwset(rwsetUpdatingMetadataFor("cc", "key"))) 221 222 validator.PreValidate(1, block) 223 224 go func() { 225 validator.PostValidate("cc", 1, 0, fmt.Errorf("")) 226 }() 227 228 err = validator.Validate("cc", 1, 1, rwsb, prp, []byte("CCEP"), []*pb.Endorsement{}) 229 assert.NoError(t, err) 230 231 pe.EvaluateRV = fmt.Errorf("policy evaluation error") 232 233 err = validator.Validate("cc", 1, 1, rwsb, prp, []byte("CCEP"), []*pb.Endorsement{}) 234 assert.Error(t, err) 235 assert.IsType(t, &errors.VSCCEndorsementPolicyError{}, err) 236 } 237 238 func TestKeylevelValidationPolicyRetrievalFailure(t *testing.T) { 239 t.Parallel() 240 241 // Scenario: we validate a transaction that updates 242 // the key-level validation parameters for a key. 243 // we simulate the case where we fail to retrieve 244 // the validation parameters from the ledger. 245 246 mr := &mockState{GetStateMetadataErr: fmt.Errorf("metadata retrieval failure")} 247 ms := &mockStateFetcher{FetchStateRv: mr} 248 pm := &KeyLevelValidationParameterManagerImpl{PolicyTranslator: &mockTranslator{}, StateFetcher: ms} 249 validator := NewKeyLevelValidator(NewV13Evaluator(&mockPolicyEvaluator{}, pm), pm) 250 251 rwsb := rwsetBytes(t, "cc") 252 prp := []byte("barf") 253 block := buildBlockWithTxs(buildTXWithRwset(rwsetUpdatingMetadataFor("cc", "key")), buildTXWithRwset(rwsetUpdatingMetadataFor("cc", "key"))) 254 255 validator.PreValidate(1, block) 256 257 go func() { 258 validator.PostValidate("cc", 1, 0, fmt.Errorf("")) 259 }() 260 261 err := validator.Validate("cc", 1, 1, rwsb, prp, []byte("CCEP"), []*pb.Endorsement{}) 262 assert.Error(t, err) 263 assert.IsType(t, &errors.VSCCExecutionFailureError{}, err) 264 } 265 266 func TestKeylevelValidationLedgerFailures(t *testing.T) { 267 // Scenario: we validate a transaction that updates 268 // the key-level validation parameters for a key. 269 // we simulate the case where we fail to retrieve 270 // the validation parameters from the ledger with 271 // both deterministic and non-deterministic errors 272 273 rwsb := rwsetBytes(t, "cc") 274 prp := []byte("barf") 275 276 t.Run("CollConfigNotDefinedError", func(t *testing.T) { 277 mr := &mockState{GetStateMetadataErr: &ledger.CollConfigNotDefinedError{Ns: "mycc"}} 278 ms := &mockStateFetcher{FetchStateRv: mr} 279 pm := &KeyLevelValidationParameterManagerImpl{PolicyTranslator: &mockTranslator{}, StateFetcher: ms} 280 validator := NewKeyLevelValidator(NewV13Evaluator(&mockPolicyEvaluator{}, pm), pm) 281 282 err := validator.Validate("cc", 1, 0, rwsb, prp, []byte("CCEP"), []*pb.Endorsement{}) 283 assert.NoError(t, err) 284 }) 285 286 t.Run("InvalidCollNameError", func(t *testing.T) { 287 mr := &mockState{GetStateMetadataErr: &ledger.InvalidCollNameError{Ns: "mycc", Coll: "mycoll"}} 288 ms := &mockStateFetcher{FetchStateRv: mr} 289 pm := &KeyLevelValidationParameterManagerImpl{PolicyTranslator: &mockTranslator{}, StateFetcher: ms} 290 validator := NewKeyLevelValidator(NewV13Evaluator(&mockPolicyEvaluator{}, pm), pm) 291 292 err := validator.Validate("cc", 1, 0, rwsb, prp, []byte("CCEP"), []*pb.Endorsement{}) 293 assert.NoError(t, err) 294 }) 295 296 t.Run("I/O error", func(t *testing.T) { 297 mr := &mockState{GetStateMetadataErr: fmt.Errorf("some I/O error")} 298 ms := &mockStateFetcher{FetchStateRv: mr} 299 pm := &KeyLevelValidationParameterManagerImpl{PolicyTranslator: &mockTranslator{}, StateFetcher: ms} 300 validator := NewKeyLevelValidator(NewV13Evaluator(&mockPolicyEvaluator{}, pm), pm) 301 302 err := validator.Validate("cc", 1, 0, rwsb, prp, []byte("CCEP"), []*pb.Endorsement{}) 303 assert.Error(t, err) 304 assert.IsType(t, &errors.VSCCExecutionFailureError{}, err) 305 }) 306 } 307 308 func TestCCEPValidation(t *testing.T) { 309 t.Parallel() 310 311 // Scenario: we validate a transaction that doesn't 312 // touch any key with a state-based endorsement policy; 313 // we expect to check the normal cc-endorsement policy. 314 315 mr := &mockState{GetStateMetadataRv: map[string][]byte{}, GetPrivateDataMetadataByHashRv: map[string][]byte{}} 316 ms := &mockStateFetcher{FetchStateRv: mr} 317 pm := &KeyLevelValidationParameterManagerImpl{PolicyTranslator: &mockTranslator{}, StateFetcher: ms} 318 pe := &mockPolicyEvaluator{} 319 validator := NewKeyLevelValidator(NewV13Evaluator(pe, pm), pm) 320 321 rwsbu := rwsetutil.NewRWSetBuilder() 322 rwsbu.AddToWriteSet("cc", "key", []byte("value")) 323 rwsbu.AddToWriteSet("cc", "key1", []byte("value")) 324 rwsbu.AddToReadSet("cc", "readkey", &version.Height{}) 325 rwsbu.AddToHashedReadSet("cc", "coll", "readpvtkey", &version.Height{}) 326 rws := rwsbu.GetTxReadWriteSet() 327 rwsb, err := rws.ToProtoBytes() 328 assert.NoError(t, err) 329 prp := []byte("barf") 330 block := buildBlockWithTxs(buildTXWithRwset(rwsetUpdatingMetadataFor("cc", "key")), buildTXWithRwset(rwsetUpdatingMetadataFor("cc", "key"))) 331 332 validator.PreValidate(1, block) 333 334 go func() { 335 validator.PostValidate("cc", 1, 0, fmt.Errorf("")) 336 }() 337 338 err = validator.Validate("cc", 1, 1, rwsb, prp, []byte("CCEP"), []*pb.Endorsement{}) 339 assert.NoError(t, err) 340 341 pe.EvaluateRV = fmt.Errorf("policy evaluation error") 342 343 err = validator.Validate("cc", 1, 1, rwsb, prp, []byte("CCEP"), []*pb.Endorsement{}) 344 assert.Error(t, err) 345 assert.IsType(t, &errors.VSCCEndorsementPolicyError{}, err) 346 } 347 348 func TestCCEPValidationReads(t *testing.T) { 349 t.Parallel() 350 351 // Scenario: we validate a transaction that doesn't 352 // touch any key with a state-based endorsement policy; 353 // we expect to check the normal cc-endorsement policy. 354 355 mr := &mockState{GetStateMetadataRv: map[string][]byte{}, GetPrivateDataMetadataByHashRv: map[string][]byte{}} 356 ms := &mockStateFetcher{FetchStateRv: mr} 357 pm := &KeyLevelValidationParameterManagerImpl{PolicyTranslator: &mockTranslator{}, StateFetcher: ms} 358 pe := &mockPolicyEvaluator{} 359 validator := NewKeyLevelValidator(NewV13Evaluator(pe, pm), pm) 360 361 rwsbu := rwsetutil.NewRWSetBuilder() 362 rwsbu.AddToReadSet("cc", "readkey", &version.Height{}) 363 rws := rwsbu.GetTxReadWriteSet() 364 rwsb, err := rws.ToProtoBytes() 365 assert.NoError(t, err) 366 prp := []byte("barf") 367 block := buildBlockWithTxs(buildTXWithRwset(rwsetUpdatingMetadataFor("cc", "key")), buildTXWithRwset(rwsetUpdatingMetadataFor("cc", "key"))) 368 369 validator.PreValidate(1, block) 370 371 go func() { 372 validator.PostValidate("cc", 1, 0, fmt.Errorf("")) 373 }() 374 375 err = validator.Validate("cc", 1, 1, rwsb, prp, []byte("CCEP"), []*pb.Endorsement{}) 376 assert.NoError(t, err) 377 378 pe.EvaluateRV = fmt.Errorf("policy evaluation error") 379 380 err = validator.Validate("cc", 1, 1, rwsb, prp, []byte("CCEP"), []*pb.Endorsement{}) 381 assert.Error(t, err) 382 assert.IsType(t, &errors.VSCCEndorsementPolicyError{}, err) 383 } 384 385 func TestOnlySBEPChecked(t *testing.T) { 386 t.Parallel() 387 388 // Scenario: we ensure that as long as there is one key that 389 // requires state-based endorsement, we only check that policy 390 // and we do not check the cc-EP. We check that by setting up the 391 // policy evaluator mock into returning an error for all policies 392 // but the state-based one, and expect successful evaluation 393 394 vpMetadataKey := pb.MetaDataKeys_VALIDATION_PARAMETER.String() 395 mr := &mockState{GetStateMetadataRv: map[string][]byte{vpMetadataKey: []byte("SBEP")}} 396 ms := &mockStateFetcher{FetchStateRv: mr} 397 pm := &KeyLevelValidationParameterManagerImpl{PolicyTranslator: &mockTranslator{}, StateFetcher: ms} 398 pe := &mockPolicyEvaluator{} 399 validator := NewKeyLevelValidator(NewV13Evaluator(pe, pm), pm) 400 401 rwsb := rwsetBytes(t, "cc") 402 prp := []byte("barf") 403 block := buildBlockWithTxs(buildTXWithRwset(rwsetUpdatingMetadataFor("cc", "key")), buildTXWithRwset(rwsetUpdatingMetadataFor("cc", "key"))) 404 405 validator.PreValidate(1, block) 406 407 go func() { 408 validator.PostValidate("cc", 1, 0, fmt.Errorf("")) 409 }() 410 411 pe.EvaluateRV = fmt.Errorf("policy evaluation error") 412 pe.EvaluateResByPolicy = map[string]error{ 413 "SBEP": nil, 414 } 415 416 err := validator.Validate("cc", 1, 1, rwsb, prp, []byte("CCEP"), []*pb.Endorsement{}) 417 assert.NoError(t, err) 418 419 // we also test with a read-write set that has a read as well as a write 420 rwsbu := rwsetutil.NewRWSetBuilder() 421 rwsbu.AddToWriteSet("cc", "key", []byte("value")) 422 rwsbu.AddToReadSet("cc", "key", nil) 423 rws := rwsbu.GetTxReadWriteSet() 424 rwsb, _ = rws.ToProtoBytes() 425 426 err = validator.Validate("cc", 1, 1, rwsb, prp, []byte("CCEP"), []*pb.Endorsement{}) 427 assert.NoError(t, err) 428 } 429 430 func TestCCEPValidationPvtReads(t *testing.T) { 431 t.Parallel() 432 433 // Scenario: we validate a transaction that doesn't 434 // touch any key with a state-based endorsement policy; 435 // we expect to check the normal cc-endorsement policy. 436 437 mr := &mockState{GetStateMetadataRv: map[string][]byte{}, GetPrivateDataMetadataByHashRv: map[string][]byte{}} 438 ms := &mockStateFetcher{FetchStateRv: mr} 439 pm := &KeyLevelValidationParameterManagerImpl{PolicyTranslator: &mockTranslator{}, StateFetcher: ms} 440 pe := &mockPolicyEvaluator{} 441 validator := NewKeyLevelValidator(NewV13Evaluator(pe, pm), pm) 442 443 rwsbu := rwsetutil.NewRWSetBuilder() 444 rwsbu.AddToHashedReadSet("cc", "coll", "readpvtkey", &version.Height{}) 445 rws := rwsbu.GetTxReadWriteSet() 446 rwsb, err := rws.ToProtoBytes() 447 assert.NoError(t, err) 448 prp := []byte("barf") 449 block := buildBlockWithTxs(buildTXWithRwset(rwsetUpdatingMetadataFor("cc", "key")), buildTXWithRwset(rwsetUpdatingMetadataFor("cc", "key"))) 450 451 validator.PreValidate(1, block) 452 453 go func() { 454 validator.PostValidate("cc", 1, 0, fmt.Errorf("")) 455 }() 456 457 err = validator.Validate("cc", 1, 1, rwsb, prp, []byte("CCEP"), []*pb.Endorsement{}) 458 assert.NoError(t, err) 459 460 pe.EvaluateRV = fmt.Errorf("policy evaluation error") 461 462 err = validator.Validate("cc", 1, 1, rwsb, prp, []byte("CCEP"), []*pb.Endorsement{}) 463 assert.Error(t, err) 464 assert.IsType(t, &errors.VSCCEndorsementPolicyError{}, err) 465 } 466 467 func TestKeylevelValidationFailure(t *testing.T) { 468 t.Parallel() 469 470 // Scenario: we validate a transaction that writes 471 // to a key that contains key-level validation params. 472 // Validation fails because the block contains a previous 473 // transaction that updates the key-level validation params 474 // for that very same key. 475 476 vpMetadataKey := pb.MetaDataKeys_VALIDATION_PARAMETER.String() 477 mr := &mockState{GetStateMetadataRv: map[string][]byte{vpMetadataKey: []byte("EP")}, GetPrivateDataMetadataByHashRv: map[string][]byte{vpMetadataKey: []byte("EP")}} 478 ms := &mockStateFetcher{FetchStateRv: mr} 479 pm := &KeyLevelValidationParameterManagerImpl{PolicyTranslator: &mockTranslator{}, StateFetcher: ms} 480 validator := NewKeyLevelValidator(NewV13Evaluator(&mockPolicyEvaluator{}, pm), pm) 481 482 rwsb := rwsetBytes(t, "cc") 483 prp := []byte("barf") 484 block := buildBlockWithTxs(buildTXWithRwset(rwsetUpdatingMetadataFor("cc", "key")), buildTXWithRwset(rwsetUpdatingMetadataFor("cc", "key"))) 485 486 validator.PreValidate(1, block) 487 488 go func() { 489 validator.PostValidate("cc", 1, 0, nil) 490 }() 491 492 err := validator.Validate("cc", 1, 1, rwsb, prp, []byte("CCEP"), []*pb.Endorsement{}) 493 assert.Error(t, err) 494 assert.IsType(t, &errors.VSCCEndorsementPolicyError{}, err) 495 }