github.com/prysmaticlabs/prysm@v1.4.4/validator/accounts/accounts_exit_test.go (about) 1 package accounts 2 3 import ( 4 "bytes" 5 "os" 6 "path/filepath" 7 "sort" 8 "testing" 9 "time" 10 11 "github.com/golang/mock/gomock" 12 ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" 13 "github.com/prysmaticlabs/prysm/shared/bytesutil" 14 "github.com/prysmaticlabs/prysm/shared/mock" 15 "github.com/prysmaticlabs/prysm/shared/testutil/assert" 16 "github.com/prysmaticlabs/prysm/shared/testutil/require" 17 "github.com/prysmaticlabs/prysm/validator/accounts/wallet" 18 "github.com/prysmaticlabs/prysm/validator/keymanager" 19 "github.com/prysmaticlabs/prysm/validator/keymanager/imported" 20 "github.com/sirupsen/logrus/hooks/test" 21 "google.golang.org/grpc/metadata" 22 "google.golang.org/protobuf/types/known/timestamppb" 23 ) 24 25 func TestExitAccountsCli_OK(t *testing.T) { 26 ctrl := gomock.NewController(t) 27 defer ctrl.Finish() 28 mockValidatorClient := mock.NewMockBeaconNodeValidatorClient(ctrl) 29 mockNodeClient := mock.NewMockNodeClient(ctrl) 30 31 mockValidatorClient.EXPECT(). 32 ValidatorIndex(gomock.Any(), gomock.Any()). 33 Return(ðpb.ValidatorIndexResponse{Index: 1}, nil) 34 35 // Any time in the past will suffice 36 genesisTime := ×tamppb.Timestamp{ 37 Seconds: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix(), 38 } 39 40 mockNodeClient.EXPECT(). 41 GetGenesis(gomock.Any(), gomock.Any()). 42 Return(ðpb.Genesis{GenesisTime: genesisTime}, nil) 43 44 mockValidatorClient.EXPECT(). 45 DomainData(gomock.Any(), gomock.Any()). 46 Return(ðpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil) 47 48 mockValidatorClient.EXPECT(). 49 ProposeExit(gomock.Any(), gomock.AssignableToTypeOf(ðpb.SignedVoluntaryExit{})). 50 Return(ðpb.ProposeExitResponse{}, nil) 51 52 walletDir, _, passwordFilePath := setupWalletAndPasswordsDir(t) 53 // Write a directory where we will import keys from. 54 keysDir := filepath.Join(t.TempDir(), "keysDir") 55 require.NoError(t, os.MkdirAll(keysDir, os.ModePerm)) 56 57 // Create keystore file in the keys directory we can then import from in our wallet. 58 keystore, _ := createKeystore(t, keysDir) 59 time.Sleep(time.Second) 60 61 // We initialize a wallet with a imported keymanager. 62 cliCtx := setupWalletCtx(t, &testWalletConfig{ 63 // Wallet configuration flags. 64 walletDir: walletDir, 65 keymanagerKind: keymanager.Imported, 66 walletPasswordFile: passwordFilePath, 67 accountPasswordFile: passwordFilePath, 68 // Flag required for ImportAccounts to work. 69 keysDir: keysDir, 70 // Flag required for ExitAccounts to work. 71 voluntaryExitPublicKeys: keystore.Pubkey, 72 }) 73 _, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{ 74 WalletCfg: &wallet.Config{ 75 WalletDir: walletDir, 76 KeymanagerKind: keymanager.Imported, 77 WalletPassword: password, 78 }, 79 }) 80 require.NoError(t, err) 81 require.NoError(t, ImportAccountsCli(cliCtx)) 82 83 validatingPublicKeys, keymanager, err := prepareWallet(cliCtx) 84 require.NoError(t, err) 85 require.NotNil(t, validatingPublicKeys) 86 require.NotNil(t, keymanager) 87 88 // Prepare user input for final confirmation step 89 var stdin bytes.Buffer 90 stdin.Write([]byte(exitPassphrase)) 91 rawPubKeys, formattedPubKeys, err := interact(cliCtx, &stdin, validatingPublicKeys) 92 require.NoError(t, err) 93 require.NotNil(t, rawPubKeys) 94 require.NotNil(t, formattedPubKeys) 95 96 cfg := PerformExitCfg{ 97 mockValidatorClient, 98 mockNodeClient, 99 keymanager, 100 rawPubKeys, 101 formattedPubKeys, 102 } 103 rawExitedKeys, formattedExitedKeys, err := PerformVoluntaryExit(cliCtx.Context, cfg) 104 require.NoError(t, err) 105 require.Equal(t, 1, len(rawExitedKeys)) 106 assert.DeepEqual(t, rawPubKeys[0], rawExitedKeys[0]) 107 require.Equal(t, 1, len(formattedExitedKeys)) 108 assert.Equal(t, "0x"+keystore.Pubkey[:12], formattedExitedKeys[0]) 109 } 110 111 func TestExitAccountsCli_OK_AllPublicKeys(t *testing.T) { 112 ctrl := gomock.NewController(t) 113 defer ctrl.Finish() 114 mockValidatorClient := mock.NewMockBeaconNodeValidatorClient(ctrl) 115 mockNodeClient := mock.NewMockNodeClient(ctrl) 116 117 mockValidatorClient.EXPECT(). 118 ValidatorIndex(gomock.Any(), gomock.Any()). 119 Return(ðpb.ValidatorIndexResponse{Index: 0}, nil) 120 121 mockValidatorClient.EXPECT(). 122 ValidatorIndex(gomock.Any(), gomock.Any()). 123 Return(ðpb.ValidatorIndexResponse{Index: 1}, nil) 124 125 // Any time in the past will suffice 126 genesisTime := ×tamppb.Timestamp{ 127 Seconds: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix(), 128 } 129 130 mockNodeClient.EXPECT(). 131 GetGenesis(gomock.Any(), gomock.Any()). 132 Times(2). 133 Return(ðpb.Genesis{GenesisTime: genesisTime}, nil) 134 135 mockValidatorClient.EXPECT(). 136 DomainData(gomock.Any(), gomock.Any()). 137 Times(2). 138 Return(ðpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil) 139 140 mockValidatorClient.EXPECT(). 141 ProposeExit(gomock.Any(), gomock.AssignableToTypeOf(ðpb.SignedVoluntaryExit{})). 142 Times(2). 143 Return(ðpb.ProposeExitResponse{}, nil) 144 145 walletDir, _, passwordFilePath := setupWalletAndPasswordsDir(t) 146 // Write a directory where we will import keys from. 147 keysDir := filepath.Join(t.TempDir(), "keysDir") 148 require.NoError(t, os.MkdirAll(keysDir, os.ModePerm)) 149 150 // Create keystore file in the keys directory we can then import from in our wallet. 151 keystore1, _ := createKeystore(t, keysDir) 152 time.Sleep(time.Second) 153 keystore2, _ := createKeystore(t, keysDir) 154 time.Sleep(time.Second) 155 156 // We initialize a wallet with a imported keymanager. 157 cliCtx := setupWalletCtx(t, &testWalletConfig{ 158 // Wallet configuration flags. 159 walletDir: walletDir, 160 keymanagerKind: keymanager.Imported, 161 walletPasswordFile: passwordFilePath, 162 accountPasswordFile: passwordFilePath, 163 // Flag required for ImportAccounts to work. 164 keysDir: keysDir, 165 // Exit all public keys. 166 exitAll: true, 167 }) 168 _, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{ 169 WalletCfg: &wallet.Config{ 170 WalletDir: walletDir, 171 KeymanagerKind: keymanager.Imported, 172 WalletPassword: password, 173 }, 174 }) 175 require.NoError(t, err) 176 require.NoError(t, ImportAccountsCli(cliCtx)) 177 178 validatingPublicKeys, keymanager, err := prepareWallet(cliCtx) 179 require.NoError(t, err) 180 require.NotNil(t, validatingPublicKeys) 181 require.NotNil(t, keymanager) 182 183 // Prepare user input for final confirmation step 184 var stdin bytes.Buffer 185 stdin.Write([]byte(exitPassphrase)) 186 rawPubKeys, formattedPubKeys, err := interact(cliCtx, &stdin, validatingPublicKeys) 187 require.NoError(t, err) 188 require.NotNil(t, rawPubKeys) 189 require.NotNil(t, formattedPubKeys) 190 191 cfg := PerformExitCfg{ 192 mockValidatorClient, 193 mockNodeClient, 194 keymanager, 195 rawPubKeys, 196 formattedPubKeys, 197 } 198 rawExitedKeys, formattedExitedKeys, err := PerformVoluntaryExit(cliCtx.Context, cfg) 199 require.NoError(t, err) 200 require.Equal(t, 2, len(rawExitedKeys)) 201 assert.DeepEqual(t, rawPubKeys, rawExitedKeys) 202 require.Equal(t, 2, len(formattedExitedKeys)) 203 wantedFormatted := []string{ 204 "0x" + keystore1.Pubkey[:12], 205 "0x" + keystore2.Pubkey[:12], 206 } 207 sort.Strings(wantedFormatted) 208 sort.Strings(formattedExitedKeys) 209 require.DeepEqual(t, wantedFormatted, formattedExitedKeys) 210 } 211 212 func TestPrepareWallet_EmptyWalletReturnsError(t *testing.T) { 213 imported.ResetCaches() 214 walletDir, _, passwordFilePath := setupWalletAndPasswordsDir(t) 215 cliCtx := setupWalletCtx(t, &testWalletConfig{ 216 walletDir: walletDir, 217 keymanagerKind: keymanager.Imported, 218 walletPasswordFile: passwordFilePath, 219 accountPasswordFile: passwordFilePath, 220 }) 221 _, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{ 222 WalletCfg: &wallet.Config{ 223 WalletDir: walletDir, 224 KeymanagerKind: keymanager.Imported, 225 WalletPassword: password, 226 }, 227 }) 228 require.NoError(t, err) 229 _, _, err = prepareWallet(cliCtx) 230 assert.ErrorContains(t, "wallet is empty", err) 231 } 232 233 func TestPrepareClients_AddsGRPCHeaders(t *testing.T) { 234 imported.ResetCaches() 235 walletDir, _, passwordFilePath := setupWalletAndPasswordsDir(t) 236 cliCtx := setupWalletCtx(t, &testWalletConfig{ 237 walletDir: walletDir, 238 keymanagerKind: keymanager.Imported, 239 walletPasswordFile: passwordFilePath, 240 accountPasswordFile: passwordFilePath, 241 grpcHeaders: "Authorization=Basic some-token,Some-Other-Header=some-value", 242 }) 243 _, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{ 244 WalletCfg: &wallet.Config{ 245 WalletDir: walletDir, 246 KeymanagerKind: keymanager.Imported, 247 WalletPassword: password, 248 }, 249 }) 250 require.NoError(t, err) 251 _, _, err = prepareClients(cliCtx) 252 require.NoError(t, err) 253 md, _ := metadata.FromOutgoingContext(cliCtx.Context) 254 assert.Equal(t, "Basic some-token", md.Get("Authorization")[0]) 255 assert.Equal(t, "some-value", md.Get("Some-Other-Header")[0]) 256 } 257 258 func TestDisplayExitInfo(t *testing.T) { 259 logHook := test.NewGlobal() 260 key := []byte("0x123456") 261 displayExitInfo([][]byte{key}, []string{string(key)}) 262 assert.LogsContain(t, logHook, "https://beaconcha.in/validator/3078313233343536") 263 } 264 265 func TestDisplayExitInfo_NoKeys(t *testing.T) { 266 logHook := test.NewGlobal() 267 displayExitInfo([][]byte{}, []string{}) 268 assert.LogsContain(t, logHook, "No successful voluntary exits") 269 } 270 271 func TestPrepareAllKeys(t *testing.T) { 272 key1 := bytesutil.ToBytes48([]byte("key1")) 273 key2 := bytesutil.ToBytes48([]byte("key2")) 274 raw, formatted := prepareAllKeys([][48]byte{key1, key2}) 275 require.Equal(t, 2, len(raw)) 276 require.Equal(t, 2, len(formatted)) 277 assert.DeepEqual(t, bytesutil.ToBytes48([]byte{107, 101, 121, 49}), bytesutil.ToBytes48(raw[0])) 278 assert.DeepEqual(t, bytesutil.ToBytes48([]byte{107, 101, 121, 50}), bytesutil.ToBytes48(raw[1])) 279 assert.Equal(t, "0x6b6579310000", formatted[0]) 280 assert.Equal(t, "0x6b6579320000", formatted[1]) 281 }