/
miner.go
289 lines (259 loc) · 8.52 KB
/
miner.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
package eth1
import (
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"path"
"strings"
"syscall"
"github.com/bazelbuild/rules_go/go/tools/bazel"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/config/params"
contracts "github.com/prysmaticlabs/prysm/v4/contracts/deposit"
"github.com/prysmaticlabs/prysm/v4/io/file"
"github.com/prysmaticlabs/prysm/v4/runtime/interop"
"github.com/prysmaticlabs/prysm/v4/testing/endtoend/helpers"
e2e "github.com/prysmaticlabs/prysm/v4/testing/endtoend/params"
e2etypes "github.com/prysmaticlabs/prysm/v4/testing/endtoend/types"
log "github.com/sirupsen/logrus"
)
const (
EthAddress = "0x878705ba3f8bc32fcf7f4caa1a35e72af65cf766"
)
// Miner represents an ETH1 node which mines blocks.
type Miner struct {
e2etypes.ComponentRunner
started chan struct{}
bootstrapEnr string
enr string
cmd *exec.Cmd
}
// NewMiner creates and returns an ETH1 node miner.
func NewMiner() *Miner {
return &Miner{
started: make(chan struct{}, 1),
}
}
// ENR returns the miner's enode.
func (m *Miner) ENR() string {
return m.enr
}
// SetBootstrapENR sets the bootstrap record.
func (m *Miner) SetBootstrapENR(bootstrapEnr string) {
m.bootstrapEnr = bootstrapEnr
}
func (*Miner) DataDir(sub ...string) string {
parts := append([]string{e2e.TestParams.TestPath, "eth1data/miner"}, sub...)
return path.Join(parts...)
}
func (*Miner) Password() string {
return KeystorePassword
}
func (m *Miner) initDataDir() error {
eth1Path := m.DataDir()
// Clear out potentially existing dir to prevent issues.
if _, err := os.Stat(eth1Path); !os.IsNotExist(err) {
if err = os.RemoveAll(eth1Path); err != nil {
return err
}
}
return nil
}
func (m *Miner) initAttempt(ctx context.Context, attempt int) (*os.File, error) {
if err := m.initDataDir(); err != nil {
return nil, err
}
// find geth so we can run it.
binaryPath, found := bazel.FindBinary("cmd/geth", "geth")
if !found {
return nil, errors.New("go-ethereum binary not found")
}
gethJsonPath := path.Join(path.Dir(binaryPath), "genesis.json")
gen := interop.GethTestnetGenesis(e2e.TestParams.Eth1GenesisTime, params.BeaconConfig())
log.Infof("eth1 miner genesis timestamp=%d", e2e.TestParams.Eth1GenesisTime)
b, err := json.Marshal(gen)
if err != nil {
return nil, err
}
if err := file.WriteFile(gethJsonPath, b); err != nil {
return nil, err
}
// write the same thing to the logs dir for inspection
gethJsonLogPath := e2e.TestParams.Logfile("genesis.json")
if err := file.WriteFile(gethJsonLogPath, b); err != nil {
return nil, err
}
initCmd := exec.CommandContext(ctx, binaryPath, "init", fmt.Sprintf("--datadir=%s", m.DataDir()), gethJsonPath) // #nosec G204 -- Safe
// redirect stderr to a log file
initFile, err := helpers.DeleteAndCreatePath(e2e.TestParams.Logfile("eth1-init_miner.log"))
if err != nil {
return nil, err
}
initCmd.Stderr = initFile
// run init command and wait until it exits. this will initialize the geth node (required before starting).
if err = initCmd.Start(); err != nil {
return nil, err
}
if err = initCmd.Wait(); err != nil {
return nil, err
}
pwFile := m.DataDir("keystore", minerPasswordFile)
args := []string{
"--nat=none", // disable nat traversal in e2e, it is failure prone and not needed
fmt.Sprintf("--datadir=%s", m.DataDir()),
fmt.Sprintf("--http.port=%d", e2e.TestParams.Ports.Eth1RPCPort),
fmt.Sprintf("--ws.port=%d", e2e.TestParams.Ports.Eth1WSPort),
fmt.Sprintf("--authrpc.port=%d", e2e.TestParams.Ports.Eth1AuthRPCPort),
fmt.Sprintf("--bootnodes=%s", m.bootstrapEnr),
fmt.Sprintf("--port=%d", e2e.TestParams.Ports.Eth1Port),
fmt.Sprintf("--networkid=%d", NetworkId),
"--http",
"--http.api=engine,net,eth",
"--http.addr=127.0.0.1",
"--http.corsdomain=\"*\"",
"--http.vhosts=\"*\"",
"--rpc.allow-unprotected-txs",
"--ws",
"--ws.api=net,eth,engine",
"--ws.addr=127.0.0.1",
"--ws.origins=\"*\"",
"--ipcdisable",
"--verbosity=4",
"--mine",
fmt.Sprintf("--unlock=%s", EthAddress),
"--allow-insecure-unlock",
"--syncmode=full",
fmt.Sprintf("--miner.etherbase=%s", EthAddress),
fmt.Sprintf("--txpool.locals=%s", EthAddress),
fmt.Sprintf("--password=%s", pwFile),
}
keystorePath, err := e2e.TestParams.Paths.MinerKeyPath()
if err != nil {
return nil, err
}
if err = file.CopyFile(keystorePath, m.DataDir("keystore", minerFile)); err != nil {
return nil, errors.Wrapf(err, "error copying %s to %s", keystorePath, m.DataDir("keystore", minerFile))
}
err = file.WriteFile(pwFile, []byte(KeystorePassword))
if err != nil {
return nil, err
}
runCmd := exec.CommandContext(ctx, binaryPath, args...) // #nosec G204 -- Safe
// redirect miner stderr to a log file
minerLog, err := helpers.DeleteAndCreatePath(e2e.TestParams.Logfile("eth1_miner.log"))
if err != nil {
return nil, err
}
runCmd.Stderr = minerLog
log.Infof("Starting eth1 miner, attempt %d, with flags: %s", attempt, strings.Join(args[2:], " "))
if err = runCmd.Start(); err != nil {
return nil, fmt.Errorf("failed to start eth1 chain: %w", err)
}
if err = helpers.WaitForTextInFile(minerLog, "Started P2P networking"); err != nil {
kerr := runCmd.Process.Kill()
if kerr != nil {
log.WithError(kerr).Error("error sending kill to failed miner command process")
}
return nil, fmt.Errorf("P2P log not found, this means the eth1 chain had issues starting: %w", err)
}
m.cmd = runCmd
return minerLog, nil
}
// Start runs a mining ETH1 node.
// The miner is responsible for moving the ETH1 chain forward and for deploying the deposit contract.
func (m *Miner) Start(ctx context.Context) error {
// give the miner start a couple of tries, since the p2p networking check is flaky
var retryErr error
var minerLog *os.File
for attempt := 0; attempt < 3; attempt++ {
minerLog, retryErr = m.initAttempt(ctx, attempt)
if retryErr == nil {
log.Infof("miner started after %d retries", attempt)
break
}
}
if retryErr != nil {
return retryErr
}
enode, err := enodeFromLogFile(minerLog.Name())
if err != nil {
return err
}
enode = "enode://" + enode + "@127.0.0.1:" + fmt.Sprintf("%d", e2e.TestParams.Ports.Eth1Port)
m.enr = enode
log.Infof("Communicated enode. Enode is %s", enode)
// Connect to the started geth dev chain.
client, err := rpc.DialHTTP(e2e.TestParams.Eth1RPCURL(e2e.MinerComponentOffset).String())
if err != nil {
return fmt.Errorf("failed to connect to ipc: %w", err)
}
web3 := ethclient.NewClient(client)
block, err := web3.BlockByNumber(ctx, nil)
if err != nil {
return err
}
log.Infof("genesis block timestamp=%d", block.Time())
eth1BlockHash := block.Hash()
e2e.TestParams.Eth1GenesisBlock = block
log.Infof("miner says genesis block root=%#x", eth1BlockHash)
cAddr := common.HexToAddress(params.BeaconConfig().DepositContractAddress)
code, err := web3.CodeAt(ctx, cAddr, nil)
if err != nil {
return err
}
log.Infof("contract code size = %d", len(code))
depositContractCaller, err := contracts.NewDepositContractCaller(cAddr, web3)
if err != nil {
return err
}
dCount, err := depositContractCaller.GetDepositCount(&bind.CallOpts{})
if err != nil {
log.Error("failed to call get_deposit_count method of deposit contract")
return err
}
log.Infof("deposit contract count=%d", dCount)
// Mark node as ready.
close(m.started)
return m.cmd.Wait()
}
// Started checks whether ETH1 node is started and ready to be queried.
func (m *Miner) Started() <-chan struct{} {
return m.started
}
// Pause pauses the component and its underlying process.
func (m *Miner) Pause() error {
return m.cmd.Process.Signal(syscall.SIGSTOP)
}
// Resume resumes the component and its underlying process.
func (m *Miner) Resume() error {
return m.cmd.Process.Signal(syscall.SIGCONT)
}
// Stop kills the component and its underlying process.
func (m *Miner) Stop() error {
return m.cmd.Process.Kill()
}
func enodeFromLogFile(name string) (string, error) {
byteContent, err := os.ReadFile(name) // #nosec G304
if err != nil {
return "", err
}
contents := string(byteContent)
searchText := "self=enode://"
startIdx := strings.Index(contents, searchText)
if startIdx == -1 {
return "", fmt.Errorf("did not find ENR text in %s", contents)
}
startIdx += len(searchText)
endIdx := strings.Index(contents[startIdx:], "@")
if endIdx == -1 {
return "", fmt.Errorf("did not find ENR text in %s", contents)
}
enode := contents[startIdx : startIdx+endIdx]
return strings.TrimPrefix(enode, "-"), nil
}