github.com/prysmaticlabs/prysm@v1.4.4/validator/client/slashing_protection_interchange_test.go (about)

     1  package client
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/hex"
     7  	"encoding/json"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/bazelbuild/rules_go/go/tools/bazel"
    12  	ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    13  	"github.com/prysmaticlabs/prysm/shared/featureconfig"
    14  	"github.com/prysmaticlabs/prysm/shared/fileutil"
    15  	"github.com/prysmaticlabs/prysm/shared/testutil"
    16  	"github.com/prysmaticlabs/prysm/shared/testutil/require"
    17  	interchangeformat "github.com/prysmaticlabs/prysm/validator/slashing-protection/local/standard-protection-format"
    18  )
    19  
    20  type eip3076TestCase struct {
    21  	Name                  string `json:"name"`
    22  	GenesisValidatorsRoot string `json:"genesis_validators_root"`
    23  	Steps                 []struct {
    24  		ShouldSucceed      bool `json:"should_succeed"`
    25  		AllowPartialImport bool `json:"allow_partial_import"`
    26  		Interchange        struct {
    27  			Metadata struct {
    28  				InterchangeFormatVersion string `json:"interchange_format_version"`
    29  				GenesisValidatorsRoot    string `json:"genesis_validators_root"`
    30  			} `json:"metadata"`
    31  			Data []struct {
    32  				Pubkey       string `json:"pubkey"`
    33  				SignedBlocks []struct {
    34  					Slot        string `json:"slot"`
    35  					SigningRoot string `json:"signing_root"`
    36  				} `json:"signed_blocks"`
    37  				SignedAttestations []struct {
    38  					SourceEpoch string `json:"source_epoch"`
    39  					TargetEpoch string `json:"target_epoch"`
    40  					SigningRoot string `json:"signing_root"`
    41  				} `json:"signed_attestations"`
    42  			} `json:"data"`
    43  		} `json:"interchange"`
    44  		Blocks []struct {
    45  			Pubkey        string `json:"pubkey"`
    46  			Slot          string `json:"slot"`
    47  			SigningRoot   string `json:"signing_root"`
    48  			ShouldSucceed bool   `json:"should_succeed"`
    49  		} `json:"blocks"`
    50  		Attestations []struct {
    51  			Pubkey        string `json:"pubkey"`
    52  			SourceEpoch   string `json:"source_epoch"`
    53  			TargetEpoch   string `json:"target_epoch"`
    54  			SigningRoot   string `json:"signing_root"`
    55  			ShouldSucceed bool   `json:"should_succeed"`
    56  		} `json:"attestations"`
    57  	} `json:"steps"`
    58  }
    59  
    60  func setupEIP3076SpecTests(t *testing.T) []*eip3076TestCase {
    61  	testFolders, err := bazel.ListRunfiles()
    62  	require.NoError(t, err)
    63  	testCases := make([]*eip3076TestCase, 0)
    64  	for _, ff := range testFolders {
    65  		if strings.Contains(ff.ShortPath, "eip3076_spec_tests") &&
    66  			strings.Contains(ff.ShortPath, "generated/") {
    67  			enc, err := fileutil.ReadFileAsBytes(ff.Path)
    68  			require.NoError(t, err)
    69  			testCase := &eip3076TestCase{}
    70  			require.NoError(t, json.Unmarshal(enc, testCase))
    71  			testCases = append(testCases, testCase)
    72  		}
    73  	}
    74  	return testCases
    75  }
    76  
    77  func TestEIP3076SpecTests(t *testing.T) {
    78  	config := &featureconfig.Flags{
    79  		SlasherProtection: true,
    80  	}
    81  	reset := featureconfig.InitWithReset(config)
    82  	defer reset()
    83  
    84  	testCases := setupEIP3076SpecTests(t)
    85  	for _, tt := range testCases {
    86  		t.Run(tt.Name, func(t *testing.T) {
    87  			if tt.Name == "" {
    88  				t.Skip("Skipping eip3076TestCase with empty name")
    89  			}
    90  			for _, step := range tt.Steps {
    91  				// Set up validator client, one new validator client per eip3076TestCase.
    92  				// This ensures we initialize a new (empty) slashing protection database.
    93  				validator, _, _, _ := setup(t)
    94  
    95  				if tt.GenesisValidatorsRoot != "" {
    96  					r, err := interchangeformat.RootFromHex(tt.GenesisValidatorsRoot)
    97  					require.NoError(t, validator.db.SaveGenesisValidatorsRoot(context.Background(), r[:]))
    98  					require.NoError(t, err)
    99  				}
   100  
   101  				// The eip3076TestCase config contains the interchange config in json.
   102  				// This loads the interchange data via ImportStandardProtectionJSON.
   103  				interchangeBytes, err := json.Marshal(step.Interchange)
   104  				if err != nil {
   105  					t.Fatal(err)
   106  				}
   107  				b := bytes.NewBuffer(interchangeBytes)
   108  				if err := interchangeformat.ImportStandardProtectionJSON(context.Background(), validator.db, b); err != nil {
   109  					if step.ShouldSucceed {
   110  						t.Fatal(err)
   111  					}
   112  				} else if !step.ShouldSucceed {
   113  					require.NotNil(t, err, "import standard protection json should have failed")
   114  				}
   115  
   116  				// This loops through a list of block signings to attempt after importing the interchange data above.
   117  				for _, sb := range step.Blocks {
   118  					bSlot, err := interchangeformat.SlotFromString(sb.Slot)
   119  					require.NoError(t, err)
   120  					pk, err := interchangeformat.PubKeyFromHex(sb.Pubkey)
   121  					require.NoError(t, err)
   122  					b := testutil.NewBeaconBlock()
   123  					b.Block.Slot = bSlot
   124  
   125  					var signingRoot [32]byte
   126  					if sb.SigningRoot != "" {
   127  						signingRootBytes, err := hex.DecodeString(strings.TrimPrefix(sb.SigningRoot, "0x"))
   128  						require.NoError(t, err)
   129  						copy(signingRoot[:], signingRootBytes)
   130  					}
   131  
   132  					err = validator.preBlockSignValidations(context.Background(), pk, b.Block, signingRoot)
   133  					if sb.ShouldSucceed {
   134  						require.NoError(t, err)
   135  					} else {
   136  						require.NotEqual(t, nil, err, "pre validation should have failed for block")
   137  					}
   138  
   139  					// Only proceed post update if pre validation did not error.
   140  					if err == nil {
   141  						err = validator.postBlockSignUpdate(context.Background(), pk, b, signingRoot)
   142  						if sb.ShouldSucceed {
   143  							require.NoError(t, err)
   144  						} else {
   145  							require.NotEqual(t, nil, err, "post validation should have failed for block")
   146  						}
   147  					}
   148  				}
   149  
   150  				// This loops through a list of attestation signings to attempt after importing the interchange data above.
   151  				for _, sa := range step.Attestations {
   152  					target, err := interchangeformat.EpochFromString(sa.TargetEpoch)
   153  					require.NoError(t, err)
   154  					source, err := interchangeformat.EpochFromString(sa.SourceEpoch)
   155  					require.NoError(t, err)
   156  					pk, err := interchangeformat.PubKeyFromHex(sa.Pubkey)
   157  					require.NoError(t, err)
   158  					ia := &ethpb.IndexedAttestation{
   159  						Data: &ethpb.AttestationData{
   160  							BeaconBlockRoot: make([]byte, 32),
   161  							Target:          &ethpb.Checkpoint{Epoch: target, Root: make([]byte, 32)},
   162  							Source:          &ethpb.Checkpoint{Epoch: source, Root: make([]byte, 32)},
   163  						},
   164  						Signature: make([]byte, 96),
   165  					}
   166  
   167  					var signingRoot [32]byte
   168  					if sa.SigningRoot != "" {
   169  						signingRootBytes, err := hex.DecodeString(strings.TrimPrefix(sa.SigningRoot, "0x"))
   170  						require.NoError(t, err)
   171  						copy(signingRoot[:], signingRootBytes)
   172  					}
   173  
   174  					err = validator.slashableAttestationCheck(context.Background(), ia, pk, signingRoot)
   175  					if sa.ShouldSucceed {
   176  						require.NoError(t, err)
   177  					} else {
   178  						require.NotNil(t, err, "pre validation should have failed for attestation")
   179  					}
   180  				}
   181  				require.NoError(t, err, validator.db.Close())
   182  			}
   183  		})
   184  	}
   185  }