github.com/prysmaticlabs/prysm@v1.4.4/validator/accounts/accounts_list_test.go (about)

     1  package accounts
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"math"
     8  	"os"
     9  	"strconv"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/golang/mock/gomock"
    14  	"github.com/google/uuid"
    15  	types "github.com/prysmaticlabs/eth2-types"
    16  	ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    17  	validatorpb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
    18  	"github.com/prysmaticlabs/prysm/shared/bls"
    19  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    20  	"github.com/prysmaticlabs/prysm/shared/event"
    21  	"github.com/prysmaticlabs/prysm/shared/mock"
    22  	"github.com/prysmaticlabs/prysm/shared/petnames"
    23  	"github.com/prysmaticlabs/prysm/shared/testutil/assert"
    24  	"github.com/prysmaticlabs/prysm/shared/testutil/require"
    25  	"github.com/prysmaticlabs/prysm/validator/accounts/wallet"
    26  	"github.com/prysmaticlabs/prysm/validator/keymanager"
    27  	"github.com/prysmaticlabs/prysm/validator/keymanager/derived"
    28  	"github.com/prysmaticlabs/prysm/validator/keymanager/imported"
    29  	"github.com/prysmaticlabs/prysm/validator/keymanager/remote"
    30  	constant "github.com/prysmaticlabs/prysm/validator/testing"
    31  	keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
    32  )
    33  
    34  type mockRemoteKeymanager struct {
    35  	publicKeys [][48]byte
    36  	opts       *remote.KeymanagerOpts
    37  }
    38  
    39  func (m *mockRemoteKeymanager) FetchValidatingPublicKeys(_ context.Context) ([][48]byte, error) {
    40  	return m.publicKeys, nil
    41  }
    42  
    43  func (m *mockRemoteKeymanager) Sign(context.Context, *validatorpb.SignRequest) (bls.Signature, error) {
    44  	return nil, nil
    45  }
    46  
    47  func (m *mockRemoteKeymanager) SubscribeAccountChanges(_ chan [][48]byte) event.Subscription {
    48  	return nil
    49  }
    50  
    51  func createRandomKeystore(t testing.TB, password string) *keymanager.Keystore {
    52  	encryptor := keystorev4.New()
    53  	id, err := uuid.NewRandom()
    54  	require.NoError(t, err)
    55  	validatingKey, err := bls.RandKey()
    56  	require.NoError(t, err)
    57  	pubKey := validatingKey.PublicKey().Marshal()
    58  	cryptoFields, err := encryptor.Encrypt(validatingKey.Marshal(), password)
    59  	require.NoError(t, err)
    60  	return &keymanager.Keystore{
    61  		Crypto:  cryptoFields,
    62  		Pubkey:  fmt.Sprintf("%x", pubKey),
    63  		ID:      id.String(),
    64  		Version: encryptor.Version(),
    65  		Name:    encryptor.Name(),
    66  	}
    67  }
    68  
    69  func TestListAccounts_ImportedKeymanager(t *testing.T) {
    70  	walletDir, passwordsDir, walletPasswordFile := setupWalletAndPasswordsDir(t)
    71  	cliCtx := setupWalletCtx(t, &testWalletConfig{
    72  		walletDir:          walletDir,
    73  		passwordsDir:       passwordsDir,
    74  		keymanagerKind:     keymanager.Imported,
    75  		walletPasswordFile: walletPasswordFile,
    76  	})
    77  	w, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{
    78  		WalletCfg: &wallet.Config{
    79  			WalletDir:      walletDir,
    80  			KeymanagerKind: keymanager.Imported,
    81  			WalletPassword: "Passwordz0320$",
    82  		},
    83  	})
    84  	require.NoError(t, err)
    85  	km, err := imported.NewKeymanager(
    86  		cliCtx.Context,
    87  		&imported.SetupConfig{
    88  			Wallet:           w,
    89  			ListenForChanges: false,
    90  		},
    91  	)
    92  	require.NoError(t, err)
    93  
    94  	numAccounts := 5
    95  	keystores := make([]*keymanager.Keystore, numAccounts)
    96  	for i := 0; i < numAccounts; i++ {
    97  		keystores[i] = createRandomKeystore(t, password)
    98  	}
    99  	require.NoError(t, km.ImportKeystores(cliCtx.Context, keystores, password))
   100  
   101  	rescueStdout := os.Stdout
   102  	r, writer, err := os.Pipe()
   103  	require.NoError(t, err)
   104  	os.Stdout = writer
   105  
   106  	// We call the list imported keymanager accounts function.
   107  	require.NoError(
   108  		t,
   109  		listImportedKeymanagerAccounts(
   110  			context.Background(),
   111  			true, /* show deposit data */
   112  			true, /*show private keys */
   113  			km,
   114  		),
   115  	)
   116  
   117  	require.NoError(t, writer.Close())
   118  	out, err := ioutil.ReadAll(r)
   119  	require.NoError(t, err)
   120  	os.Stdout = rescueStdout
   121  
   122  	// Get stdout content and split to lines
   123  	newLine := fmt.Sprintln()
   124  	lines := strings.Split(string(out), newLine)
   125  
   126  	// Expected output example:
   127  	/*
   128  		(keymanager kind) imported wallet
   129  
   130  		Showing 5 validator accounts
   131  		View the eth1 deposit transaction data for your accounts by running `validator accounts list --show-deposit-data
   132  
   133  		Account 0 | fully-evolving-fawn
   134  		[validating public key] 0xa6669aa0381c06470b9a6faf8abf4194ad5148a62e461cbef5a6bc4d292026f58b992c4cf40e50552d301cef19da75b9
   135  		[validating private key] 0x50cabc13435fcbde9d240fe720aff84f8557a6c1c445211b904f1a9620668241
   136  		If you imported your account coming from the Ethereum launchpad, you will find your deposit_data.json in the eth2.0-deposit-cli's validator_keys folder
   137  
   138  
   139  		Account 1 | preferably-mighty-heron
   140  		[validating public key] 0xa7ea37fa2e2272762ffed8486f09b13cd56d76cf03a2a3e75bc36bd1719add84c20597671750be5bc1ccd3dadfebc30f
   141  		[validating private key] 0x44563da0d11bc6a7219d18217cce8cdd064de3ebee5cdcf8d901c2fae7545116
   142  		If you imported your account coming from the Ethereum eth2 launchpad, you will find your deposit_data.json in the eth2.0-deposit-cli's validator_keys folder
   143  
   144  
   145  		Account 2 | conversely-good-monitor
   146  		[validating public key] 0xa4c63619fb8cb87f6dd1686c9255f99c68066797bf284488ecbab64b1926d33eefdf96d1ee89ae4a89e84e7fb019d5e5
   147  		[validating private key] 0x4448d0ab17ecd73bbb636ddbfc89b181731f6cd88c33f2cecc0d04cba1a18447
   148  		If you imported your account coming from the Ethereum eth2 launchpad, you will find your deposit_data.json in the eth2.0-deposit-cli's validator_keys folder
   149  
   150  
   151  		Account 3 | rarely-joint-mako
   152  		[validating public key] 0x91dd8d5bfc22aea398740ebcea66ced159df8d3f1a066d7aba9f0bef4ed6d9687fc1fd1c87bd2b6d12b0788dfb6a7d20
   153  		[validating private key] 0x4d1944bd7375185f70b3e70c68d9e6307f2009de3a4cf47ca5217443ddf81fc9
   154  		If you imported your account coming from the Ethereum eth2 launchpad, you will find your deposit_data.json in the eth2.0-deposit-cli's validator_keys folder
   155  
   156  
   157  		Account 4 | mainly-useful-catfish
   158  		[validating public key] 0x83c4d722a98b599e2666bbe35146ff44800256190bc662f2dd5efbc0c4c0d57e5d297487a4f9c21a932d3b1b40e8379f
   159  		[validating private key] 0x284cd65030496bf82ee2d52963cd540a1abb2cc738b8164901bbe7e2df4d57bd
   160  		If you imported your account coming from the Ethereum eth2 launchpad, you will find your deposit_data.json in the eth2.0-deposit-cli's validator_keys folder
   161  
   162  
   163  
   164  	*/
   165  
   166  	// Expected output format definition
   167  	const prologLength = 4
   168  	const accountLength = 6
   169  	const epilogLength = 2
   170  	const nameOffset = 1
   171  	const keyOffset = 2
   172  	const privkeyOffset = 3
   173  
   174  	// Require the output has correct number of lines
   175  	lineCount := prologLength + accountLength*numAccounts + epilogLength
   176  	require.Equal(t, lineCount, len(lines))
   177  
   178  	// Assert the keymanager kind is printed on the first line.
   179  	kindString := "imported"
   180  	kindFound := strings.Contains(lines[0], kindString)
   181  	assert.Equal(t, true, kindFound, "Keymanager Kind %s not found on the first line", kindString)
   182  
   183  	// Get account names and require the correct count
   184  	accountNames, err := km.ValidatingAccountNames()
   185  	require.NoError(t, err)
   186  	require.Equal(t, numAccounts, len(accountNames))
   187  
   188  	// Assert that account names are printed on the correct lines
   189  	for i, accountName := range accountNames {
   190  		lineNumber := prologLength + accountLength*i + nameOffset
   191  		accountNameFound := strings.Contains(lines[lineNumber], accountName)
   192  		assert.Equal(t, true, accountNameFound, "Account Name %s not found on line number %d", accountName, lineNumber)
   193  	}
   194  
   195  	// Get public keys and require the correct count
   196  	pubKeys, err := km.FetchValidatingPublicKeys(cliCtx.Context)
   197  	require.NoError(t, err)
   198  	require.Equal(t, numAccounts, len(pubKeys))
   199  
   200  	// Assert that public keys are printed on the correct lines
   201  	for i, key := range pubKeys {
   202  		lineNumber := prologLength + accountLength*i + keyOffset
   203  		keyString := fmt.Sprintf("%#x", key)
   204  		keyFound := strings.Contains(lines[lineNumber], keyString)
   205  		assert.Equal(t, true, keyFound, "Public Key %s not found on line number %d", keyString, lineNumber)
   206  	}
   207  
   208  	// Get private keys and require the correct count
   209  	privKeys, err := km.FetchValidatingPrivateKeys(cliCtx.Context)
   210  	require.NoError(t, err)
   211  	require.Equal(t, numAccounts, len(pubKeys))
   212  
   213  	// Assert that private keys are printed on the correct lines
   214  	for i, key := range privKeys {
   215  		lineNumber := prologLength + accountLength*i + privkeyOffset
   216  		keyString := fmt.Sprintf("%#x", key)
   217  		keyFound := strings.Contains(lines[lineNumber], keyString)
   218  		assert.Equal(t, true, keyFound, "Private Key %s not found on line number %d", keyString, lineNumber)
   219  	}
   220  }
   221  
   222  func TestListAccounts_DerivedKeymanager(t *testing.T) {
   223  	walletDir, passwordsDir, passwordFilePath := setupWalletAndPasswordsDir(t)
   224  	cliCtx := setupWalletCtx(t, &testWalletConfig{
   225  		walletDir:          walletDir,
   226  		passwordsDir:       passwordsDir,
   227  		keymanagerKind:     keymanager.Derived,
   228  		walletPasswordFile: passwordFilePath,
   229  	})
   230  	w, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{
   231  		WalletCfg: &wallet.Config{
   232  			WalletDir:      walletDir,
   233  			KeymanagerKind: keymanager.Derived,
   234  			WalletPassword: "Passwordz0320$",
   235  		},
   236  	})
   237  	require.NoError(t, err)
   238  
   239  	keymanager, err := derived.NewKeymanager(
   240  		cliCtx.Context,
   241  		&derived.SetupConfig{
   242  			Wallet:           w,
   243  			ListenForChanges: false,
   244  		},
   245  	)
   246  	require.NoError(t, err)
   247  
   248  	numAccounts := 5
   249  	err = keymanager.RecoverAccountsFromMnemonic(cliCtx.Context, constant.TestMnemonic, "", numAccounts)
   250  	require.NoError(t, err)
   251  
   252  	rescueStdout := os.Stdout
   253  	r, writer, err := os.Pipe()
   254  	require.NoError(t, err)
   255  	os.Stdout = writer
   256  
   257  	// We call the list imported keymanager accounts function.
   258  	require.NoError(t, listDerivedKeymanagerAccounts(cliCtx.Context, true, keymanager))
   259  
   260  	require.NoError(t, writer.Close())
   261  	out, err := ioutil.ReadAll(r)
   262  	require.NoError(t, err)
   263  	os.Stdout = rescueStdout
   264  
   265  	// Get stdout content and split to lines
   266  	newLine := fmt.Sprintln()
   267  	lines := strings.Split(string(out), newLine)
   268  
   269  	// Expected output example:
   270  	/*
   271  		(keymanager kind) derived, (HD) hierarchical-deterministic
   272  		(derivation format) m / purpose / coin_type / account_index / withdrawal_key / validating_key
   273  		Showing 2 validator accounts
   274  
   275  		Account 0 | uniquely-sunny-tarpon
   276  		[withdrawal public key] 0xa5faa97252104b408340b5d8cae3fa01023fa4dc9e7c7b470821433cf3a2a18158410b7d8a6dcdcd176c6552c2526681
   277  		[withdrawal private key] 0x5266fd1f13d7af74614fde4fed3b664bfd529bc4ad91118e3db73647b99546df
   278  		[derivation path] m/12381/3600/0/0
   279  		[validating public key] 0xa7292d8f8d1c1f3d42cacefd2fc4cd3b82651be37c1eb790bbd294a874829f4b7e1c167345dcc1966cc844132b38097e
   280  		[validating private key] 0x590707187dae64b42b8d36a95f3d7e11313ddd8b8d871b09e478e08c9bc8740b
   281  		[derivation path] m/12381/3600/0/0/0
   282  
   283  		======================Eth1 Deposit Transaction Data=====================
   284  
   285  		0x22895118000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001205a9e92992d6a97ad113d217fa35cbe0659c662afe913ffd3a3ba61d7473be5630000000000000000000000000000000000000000000000000000000000000030a7292d8f8d1c1f3d42cacefd2fc4cd3b82651be37c1eb790bbd294a874829f4b7e1c167345dcc1966cc844132b38097e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020003b8f70706c37fb0b8dcbd95340889bad7d7f29121ea895052a8b216de95e480000000000000000000000000000000000000000000000000000000000000060b6727242b055448defbf54292c65e30ae28ca3aef8a07c8fe674abc0ca42a324be2e7592d3e45bba84ca364d7fe1f0ce073bf8b3692246395aa127cdbf93c64ae9ca48f85cb4b1e519f6821998181de1c7465b2bdcae4ddd0dbc2d02a56219d9
   286  
   287  		===================================================================
   288  
   289  		Account 1 | usually-obliging-pelican
   290  		[withdrawal public key] 0xb91840d33bb87338bb28605cff837acd50e43a174a8a6d3893108fb91217fa428c12f1b2a25cf3c7aca75d418bcf0384
   291  		[withdrawal private key] 0x72c5ffa7d08fb16cd35a9cb10494dfd49b46842ea1bcc1a4cf46b46680b66810
   292  		[derivation path] m/12381/3600/1/0
   293  		[validating public key] 0x8447f878b701dad4dfa5a884cebc4745b0e8f21340dc56c840826537764dcc54e2e68f80b8d4e5737180212a26211891
   294  		[validating private key] 0x2cd5b1cddc9d96e50a16bea05d0953447655e3dd59fa1bfefad467c73d6c164a
   295  		[derivation path] m/12381/3600/1/0/0
   296  
   297  		======================Eth1 Deposit Transaction Data=====================
   298  
   299  		0x22895118000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001200a0b9079c33cc40d602a50f5c51f6db30b0f959fc6f58048d6d43319fea6c09000000000000000000000000000000000000000000000000000000000000000308447f878b701dad4dfa5a884cebc4745b0e8f21340dc56c840826537764dcc54e2e68f80b8d4e5737180212a2621189100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000d6ac42bde23388e7428c1247364347c027c3507e461d68b851d506c60364cf0000000000000000000000000000000000000000000000000000000000000060801a2d432595164d7d88ae1695618db511d1507108573b8471098536b2b5a23f6711235f0a9c6fa65ac26cbd0f2d97e013e0c72ab6b5cff406c48d99ec0a2439aa9faa4557d20bb210d451519101616fa20b1ff2c67fae561cdff160fbc7dc98
   300  
   301  		===================================================================
   302  
   303  
   304  	*/
   305  
   306  	// Expected output format definition
   307  	const prologLength = 3
   308  	const accountLength = 6
   309  	const epilogLength = 1
   310  	const nameOffset = 1
   311  	const keyOffset = 2
   312  	const validatingPrivateKeyOffset = 3
   313  
   314  	// Require the output has correct number of lines
   315  	lineCount := prologLength + accountLength*numAccounts + epilogLength
   316  	require.Equal(t, lineCount, len(lines))
   317  
   318  	// Assert the keymanager kind is printed on the first line.
   319  	kindString := w.KeymanagerKind().String()
   320  	kindFound := strings.Contains(lines[0], kindString)
   321  	assert.Equal(t, true, kindFound, "Keymanager Kind %s not found on the first line", kindString)
   322  
   323  	// Get account names and require the correct count
   324  	accountNames, err := keymanager.ValidatingAccountNames(cliCtx.Context)
   325  	require.NoError(t, err)
   326  	require.Equal(t, numAccounts, len(accountNames))
   327  
   328  	// Assert that account names are printed on the correct lines
   329  	for i, accountName := range accountNames {
   330  		lineNumber := prologLength + accountLength*i + nameOffset
   331  		accountNameFound := strings.Contains(lines[lineNumber], accountName)
   332  		assert.Equal(t, true, accountNameFound, "Account Name %s not found on line number %d", accountName, lineNumber)
   333  	}
   334  
   335  	// Get public keys and require the correct count
   336  	pubKeys, err := keymanager.FetchValidatingPublicKeys(cliCtx.Context)
   337  	require.NoError(t, err)
   338  	require.Equal(t, numAccounts, len(pubKeys))
   339  
   340  	// Assert that public keys are printed on the correct lines
   341  	for i, key := range pubKeys {
   342  		lineNumber := prologLength + accountLength*i + keyOffset
   343  		keyString := fmt.Sprintf("%#x", key)
   344  		keyFound := strings.Contains(lines[lineNumber], keyString)
   345  		assert.Equal(t, true, keyFound, "Public Key %s not found on line number %d", keyString, lineNumber)
   346  	}
   347  
   348  	// Get validating private keys and require the correct count
   349  	validatingPrivKeys, err := keymanager.FetchValidatingPrivateKeys(cliCtx.Context)
   350  	require.NoError(t, err)
   351  	require.Equal(t, numAccounts, len(pubKeys))
   352  
   353  	// Assert that validating private keys are printed on the correct lines
   354  	for i, key := range validatingPrivKeys {
   355  		lineNumber := prologLength + accountLength*i + validatingPrivateKeyOffset
   356  		keyString := fmt.Sprintf("%#x", key)
   357  		keyFound := strings.Contains(lines[lineNumber], keyString)
   358  		assert.Equal(t, true, keyFound, "Validating Private Key %s not found on line number %d", keyString, lineNumber)
   359  	}
   360  }
   361  
   362  func TestListAccounts_RemoteKeymanager(t *testing.T) {
   363  	walletDir, _, _ := setupWalletAndPasswordsDir(t)
   364  	cliCtx := setupWalletCtx(t, &testWalletConfig{
   365  		walletDir:      walletDir,
   366  		keymanagerKind: keymanager.Remote,
   367  	})
   368  	w, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{
   369  		WalletCfg: &wallet.Config{
   370  			WalletDir:      walletDir,
   371  			KeymanagerKind: keymanager.Remote,
   372  			WalletPassword: password,
   373  		},
   374  	})
   375  	require.NoError(t, err)
   376  
   377  	rescueStdout := os.Stdout
   378  	r, writer, err := os.Pipe()
   379  	require.NoError(t, err)
   380  	os.Stdout = writer
   381  
   382  	numAccounts := 3
   383  	pubKeys := make([][48]byte, numAccounts)
   384  	for i := 0; i < numAccounts; i++ {
   385  		key := make([]byte, 48)
   386  		copy(key, strconv.Itoa(i))
   387  		pubKeys[i] = bytesutil.ToBytes48(key)
   388  	}
   389  	km := &mockRemoteKeymanager{
   390  		publicKeys: pubKeys,
   391  		opts: &remote.KeymanagerOpts{
   392  			RemoteCertificate: &remote.CertificateConfig{
   393  				RequireTls:     true,
   394  				ClientCertPath: "/tmp/client.crt",
   395  				ClientKeyPath:  "/tmp/client.key",
   396  				CACertPath:     "/tmp/ca.crt",
   397  			},
   398  			RemoteAddr: "localhost:4000",
   399  		},
   400  	}
   401  	// We call the list remote keymanager accounts function.
   402  	require.NoError(t, listRemoteKeymanagerAccounts(context.Background(), w, km, km.opts))
   403  
   404  	require.NoError(t, writer.Close())
   405  	out, err := ioutil.ReadAll(r)
   406  	require.NoError(t, err)
   407  	os.Stdout = rescueStdout
   408  
   409  	// Get stdout content and split to lines
   410  	newLine := fmt.Sprintln()
   411  	lines := strings.Split(string(out), newLine)
   412  
   413  	// Expected output example:
   414  	/*
   415  		(keymanager kind) remote signer
   416  		(configuration file path) /tmp/79336/wallet/remote/keymanageropts.json
   417  
   418  		Configuration options
   419  		Remote gRPC address: localhost:4000
   420  		Require TLS: true
   421  		Client cert path: /tmp/client.crt
   422  		Client key path: /tmp/client.key
   423  		CA cert path: /tmp/ca.crt
   424  
   425  		Showing 3 validator accounts
   426  
   427  		equally-primary-foal
   428  		[validating public key] 0x300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
   429  
   430  
   431  		rationally-charmed-werewolf
   432  		[validating public key] 0x310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
   433  
   434  
   435  	*/
   436  
   437  	// Expected output format definition
   438  	const prologLength = 11
   439  	const configOffset = 4
   440  	const configLength = 5
   441  	const accountLength = 4
   442  	const nameOffset = 1
   443  	const keyOffset = 2
   444  	const epilogLength = 1
   445  
   446  	// Require the output has correct number of lines
   447  	lineCount := prologLength + accountLength*numAccounts + epilogLength
   448  	require.Equal(t, lineCount, len(lines))
   449  
   450  	// Assert the keymanager kind is printed on the first line.
   451  	kindString := w.KeymanagerKind().String()
   452  	kindFound := strings.Contains(lines[0], kindString)
   453  	assert.Equal(t, true, kindFound, "Keymanager Kind %s not found on the first line", kindString)
   454  
   455  	// Assert that Configuration is printed in the right position
   456  	configLines := lines[configOffset:(configOffset + configLength)]
   457  	configExpected := km.opts.String()
   458  	configActual := fmt.Sprintln(strings.Join(configLines, newLine))
   459  	assert.Equal(t, configExpected, configActual, "Configuration not found at the expected position")
   460  
   461  	// Assert that account names are printed on the correct lines
   462  	for i := 0; i < numAccounts; i++ {
   463  		lineNumber := prologLength + accountLength*i + nameOffset
   464  		accountName := petnames.DeterministicName(pubKeys[i][:], "-")
   465  		accountNameFound := strings.Contains(lines[lineNumber], accountName)
   466  		assert.Equal(t, true, accountNameFound, "Account Name %s not found on line number %d", accountName, lineNumber)
   467  	}
   468  
   469  	// Assert that public keys are printed on the correct lines
   470  	for i, key := range pubKeys {
   471  		lineNumber := prologLength + accountLength*i + keyOffset
   472  		keyString := fmt.Sprintf("%#x", key)
   473  		keyFound := strings.Contains(lines[lineNumber], keyString)
   474  		assert.Equal(t, true, keyFound, "Public Key %s not found on line number %d", keyString, lineNumber)
   475  	}
   476  }
   477  
   478  func TestListAccounts_ListValidatorIndices(t *testing.T) {
   479  	ctrl := gomock.NewController(t)
   480  	defer ctrl.Finish()
   481  
   482  	numAccounts := 3
   483  	pubKeys := make([][48]byte, numAccounts)
   484  	pks := make([][]byte, numAccounts)
   485  
   486  	for i := 0; i < numAccounts; i++ {
   487  		key := make([]byte, 48)
   488  		copy(key, strconv.Itoa(i))
   489  		pubKeys[i] = bytesutil.ToBytes48(key)
   490  		pks[i] = key
   491  	}
   492  
   493  	km := &mockRemoteKeymanager{
   494  		publicKeys: pubKeys,
   495  	}
   496  
   497  	rescueStdout := os.Stdout
   498  	r, writer, err := os.Pipe()
   499  	require.NoError(t, err)
   500  	os.Stdout = writer
   501  
   502  	m := mock.NewMockBeaconNodeValidatorClient(ctrl)
   503  
   504  	req := &ethpb.MultipleValidatorStatusRequest{PublicKeys: pks}
   505  	resp := &ethpb.MultipleValidatorStatusResponse{Indices: []types.ValidatorIndex{1, math.MaxUint64, 2}}
   506  
   507  	m.
   508  		EXPECT().
   509  		MultipleValidatorStatus(gomock.Eq(context.Background()), gomock.Eq(req)).
   510  		Return(resp, nil)
   511  
   512  	require.NoError(
   513  		t,
   514  		listValidatorIndices(
   515  			context.Background(),
   516  			km,
   517  			m,
   518  		),
   519  	)
   520  
   521  	require.NoError(t, writer.Close())
   522  	out, err := ioutil.ReadAll(r)
   523  	require.NoError(t, err)
   524  	os.Stdout = rescueStdout
   525  
   526  	expectedStdout := au.BrightGreen("Validator indices:").Bold().String() + "\n0x30000000: 1\n0x32000000: 2\n"
   527  	require.Equal(t, expectedStdout, string(out))
   528  }