github.com/prysmaticlabs/prysm@v1.4.4/endtoend/components/validator.go (about) 1 package components 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io/ioutil" 8 "math/big" 9 "os" 10 "os/exec" 11 "path" 12 "strings" 13 14 "github.com/bazelbuild/rules_go/go/tools/bazel" 15 "github.com/ethereum/go-ethereum/accounts/abi/bind" 16 "github.com/ethereum/go-ethereum/accounts/keystore" 17 "github.com/ethereum/go-ethereum/ethclient" 18 "github.com/ethereum/go-ethereum/rpc" 19 "github.com/pkg/errors" 20 "github.com/prysmaticlabs/prysm/cmd/validator/flags" 21 contracts "github.com/prysmaticlabs/prysm/contracts/deposit-contract" 22 "github.com/prysmaticlabs/prysm/endtoend/helpers" 23 e2e "github.com/prysmaticlabs/prysm/endtoend/params" 24 e2etypes "github.com/prysmaticlabs/prysm/endtoend/types" 25 "github.com/prysmaticlabs/prysm/shared/bytesutil" 26 cmdshared "github.com/prysmaticlabs/prysm/shared/cmd" 27 "github.com/prysmaticlabs/prysm/shared/featureconfig" 28 "github.com/prysmaticlabs/prysm/shared/params" 29 "github.com/prysmaticlabs/prysm/shared/testutil" 30 ) 31 32 const depositGasLimit = 4000000 33 34 var _ e2etypes.ComponentRunner = (*ValidatorNode)(nil) 35 var _ e2etypes.ComponentRunner = (*ValidatorNodeSet)(nil) 36 37 // ValidatorNodeSet represents set of validator nodes. 38 type ValidatorNodeSet struct { 39 e2etypes.ComponentRunner 40 config *e2etypes.E2EConfig 41 started chan struct{} 42 } 43 44 // NewValidatorNodeSet creates and returns a set of validator nodes. 45 func NewValidatorNodeSet(config *e2etypes.E2EConfig) *ValidatorNodeSet { 46 return &ValidatorNodeSet{ 47 config: config, 48 started: make(chan struct{}, 1), 49 } 50 } 51 52 // Start starts the configured amount of validators, also sending and mining their deposits. 53 func (s *ValidatorNodeSet) Start(ctx context.Context) error { 54 // Always using genesis count since using anything else would be difficult to test for. 55 validatorNum := int(params.BeaconConfig().MinGenesisActiveValidatorCount) 56 beaconNodeNum := e2e.TestParams.BeaconNodeCount 57 if validatorNum%beaconNodeNum != 0 { 58 return errors.New("validator count is not easily divisible by beacon node count") 59 } 60 validatorsPerNode := validatorNum / beaconNodeNum 61 62 // Create validator nodes. 63 nodes := make([]e2etypes.ComponentRunner, beaconNodeNum) 64 for i := 0; i < beaconNodeNum; i++ { 65 nodes[i] = NewValidatorNode(s.config, validatorsPerNode, i, validatorsPerNode*i) 66 } 67 68 // Wait for all nodes to finish their job (blocking). 69 // Once nodes are ready passed in handler function will be called. 70 return helpers.WaitOnNodes(ctx, nodes, func() { 71 // All nodes stated, close channel, so that all services waiting on a set, can proceed. 72 close(s.started) 73 }) 74 } 75 76 // Started checks whether validator node set is started and all nodes are ready to be queried. 77 func (s *ValidatorNodeSet) Started() <-chan struct{} { 78 return s.started 79 } 80 81 // ValidatorNode represents a validator node. 82 type ValidatorNode struct { 83 e2etypes.ComponentRunner 84 config *e2etypes.E2EConfig 85 started chan struct{} 86 validatorNum int 87 index int 88 offset int 89 } 90 91 // NewValidatorNode creates and returns a validator node. 92 func NewValidatorNode(config *e2etypes.E2EConfig, validatorNum, index, offset int) *ValidatorNode { 93 return &ValidatorNode{ 94 config: config, 95 validatorNum: validatorNum, 96 index: index, 97 offset: offset, 98 started: make(chan struct{}, 1), 99 } 100 } 101 102 // Start starts a validator client. 103 func (v *ValidatorNode) Start(ctx context.Context) error { 104 var pkg, target string 105 if v.config.UsePrysmShValidator { 106 pkg = "" 107 target = "prysm_sh" 108 } else { 109 pkg = "cmd/validator" 110 target = "validator" 111 } 112 binaryPath, found := bazel.FindBinary(pkg, target) 113 if !found { 114 return errors.New("validator binary not found") 115 } 116 117 config, validatorNum, index, offset := v.config, v.validatorNum, v.index, v.offset 118 beaconRPCPort := e2e.TestParams.BeaconNodeRPCPort + index 119 if beaconRPCPort >= e2e.TestParams.BeaconNodeRPCPort+e2e.TestParams.BeaconNodeCount { 120 // Point any extra validator clients to a node we know is running. 121 beaconRPCPort = e2e.TestParams.BeaconNodeRPCPort 122 } 123 124 file, err := helpers.DeleteAndCreateFile(e2e.TestParams.LogPath, fmt.Sprintf(e2e.ValidatorLogFileName, index)) 125 if err != nil { 126 return err 127 } 128 gFile, err := helpers.GraffitiYamlFile(e2e.TestParams.TestPath) 129 if err != nil { 130 return err 131 } 132 args := []string{ 133 fmt.Sprintf("--%s=%s/eth2-val-%d", cmdshared.DataDirFlag.Name, e2e.TestParams.TestPath, index), 134 fmt.Sprintf("--%s=%s", cmdshared.LogFileName.Name, file.Name()), 135 fmt.Sprintf("--%s=%s", flags.GraffitiFileFlag.Name, gFile), 136 fmt.Sprintf("--%s=%d", flags.InteropNumValidators.Name, validatorNum), 137 fmt.Sprintf("--%s=%d", flags.InteropStartIndex.Name, offset), 138 fmt.Sprintf("--%s=%d", flags.MonitoringPortFlag.Name, e2e.TestParams.ValidatorMetricsPort+index), 139 fmt.Sprintf("--%s=%d", flags.GRPCGatewayPort.Name, e2e.TestParams.ValidatorGatewayPort+index), 140 fmt.Sprintf("--%s=localhost:%d", flags.BeaconRPCProviderFlag.Name, beaconRPCPort), 141 fmt.Sprintf("--%s=%s", flags.GrpcHeadersFlag.Name, "dummy=value,foo=bar"), // Sending random headers shouldn't break anything. 142 fmt.Sprintf("--%s=%s", cmdshared.VerbosityFlag.Name, "debug"), 143 "--" + cmdshared.ForceClearDB.Name, 144 "--" + cmdshared.E2EConfigFlag.Name, 145 "--" + cmdshared.AcceptTosFlag.Name, 146 } 147 // Only apply e2e flags to the current branch. New flags may not exist in previous release. 148 if !v.config.UsePrysmShValidator { 149 args = append(args, featureconfig.E2EValidatorFlags...) 150 } 151 args = append(args, config.ValidatorFlags...) 152 153 if v.config.UsePrysmShValidator { 154 args = append([]string{"validator"}, args...) 155 log.Warning("Using latest release validator via prysm.sh") 156 } 157 158 cmd := exec.CommandContext(ctx, binaryPath, args...) 159 160 // Write stdout and stderr to log files. 161 stdout, err := os.Create(path.Join(e2e.TestParams.LogPath, fmt.Sprintf("validator_%d_stdout.log", index))) 162 if err != nil { 163 return err 164 } 165 stderr, err := os.Create(path.Join(e2e.TestParams.LogPath, fmt.Sprintf("validator_%d_stderr.log", index))) 166 if err != nil { 167 return err 168 } 169 defer func() { 170 if err := stdout.Close(); err != nil { 171 log.WithError(err).Error("Failed to close stdout file") 172 } 173 if err := stderr.Close(); err != nil { 174 log.WithError(err).Error("Failed to close stderr file") 175 } 176 }() 177 cmd.Stdout = stdout 178 cmd.Stderr = stderr 179 180 log.Infof("Starting validator client %d with flags: %s %s", index, binaryPath, strings.Join(args, " ")) 181 if err = cmd.Start(); err != nil { 182 return err 183 } 184 185 // Mark node as ready. 186 close(v.started) 187 188 return cmd.Wait() 189 } 190 191 // Started checks whether validator node is started and ready to be queried. 192 func (v *ValidatorNode) Started() <-chan struct{} { 193 return v.started 194 } 195 196 // SendAndMineDeposits sends the requested amount of deposits and mines the chain after to ensure the deposits are seen. 197 func SendAndMineDeposits(keystorePath string, validatorNum, offset int, partial bool) error { 198 client, err := rpc.DialHTTP(fmt.Sprintf("http://127.0.0.1:%d", e2e.TestParams.Eth1RPCPort)) 199 if err != nil { 200 return err 201 } 202 defer client.Close() 203 web3 := ethclient.NewClient(client) 204 205 keystoreBytes, err := ioutil.ReadFile(keystorePath) 206 if err != nil { 207 return err 208 } 209 if err = sendDeposits(web3, keystoreBytes, validatorNum, offset, partial); err != nil { 210 return err 211 } 212 mineKey, err := keystore.DecryptKey(keystoreBytes, "" /*password*/) 213 if err != nil { 214 return err 215 } 216 if err = mineBlocks(web3, mineKey, params.BeaconConfig().Eth1FollowDistance); err != nil { 217 return fmt.Errorf("failed to mine blocks %w", err) 218 } 219 return nil 220 } 221 222 // sendDeposits uses the passed in web3 and keystore bytes to send the requested deposits. 223 func sendDeposits(web3 *ethclient.Client, keystoreBytes []byte, num, offset int, partial bool) error { 224 txOps, err := bind.NewTransactorWithChainID(bytes.NewReader(keystoreBytes), "" /*password*/, big.NewInt(1337)) 225 if err != nil { 226 return err 227 } 228 txOps.GasLimit = depositGasLimit 229 txOps.Context = context.Background() 230 nonce, err := web3.PendingNonceAt(context.Background(), txOps.From) 231 if err != nil { 232 return err 233 } 234 txOps.Nonce = big.NewInt(int64(nonce)) 235 236 contract, err := contracts.NewDepositContract(e2e.TestParams.ContractAddress, web3) 237 if err != nil { 238 return err 239 } 240 241 balances := make([]uint64, num+offset) 242 for i := 0; i < len(balances); i++ { 243 if i < len(balances)/2 && partial { 244 balances[i] = params.BeaconConfig().MaxEffectiveBalance / 2 245 } else { 246 balances[i] = params.BeaconConfig().MaxEffectiveBalance 247 } 248 } 249 deposits, trie, err := testutil.DepositsWithBalance(balances) 250 if err != nil { 251 return err 252 } 253 allDeposits := deposits 254 allRoots := trie.Items() 255 allBalances := balances 256 if partial { 257 deposits2, trie2, err := testutil.DepositsWithBalance(balances) 258 if err != nil { 259 return err 260 } 261 allDeposits = append(deposits, deposits2[:len(balances)/2]...) 262 allRoots = append(trie.Items(), trie2.Items()[:len(balances)/2]...) 263 allBalances = append(balances, balances[:len(balances)/2]...) 264 } 265 for index, dd := range allDeposits { 266 if index < offset { 267 continue 268 } 269 depositInGwei := big.NewInt(int64(allBalances[index])) 270 txOps.Value = depositInGwei.Mul(depositInGwei, big.NewInt(int64(params.BeaconConfig().GweiPerEth))) 271 _, err = contract.Deposit(txOps, dd.Data.PublicKey, dd.Data.WithdrawalCredentials, dd.Data.Signature, bytesutil.ToBytes32(allRoots[index])) 272 if err != nil { 273 return errors.Wrap(err, "unable to send transaction to contract") 274 } 275 txOps.Nonce = txOps.Nonce.Add(txOps.Nonce, big.NewInt(1)) 276 } 277 return nil 278 }