Skip to content
This repository was archived by the owner on Dec 11, 2019. It is now read-only.

Commit 72820e8

Browse files
committed
Merge pull request #14734 from Slava/feature/ethwallet
Eth-wallet rebased
1 parent c4d17c3 commit 72820e8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+6558
-5066
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ Brave.tar.bz2
5858
app/extensions/gen
5959
app/extensions/brave/gen
6060
app/extensions/torrent/gen
61+
app/extensions/ethwallet
6162
*.pfx
6263
js/constants/buildConfig.js
6364

@@ -84,3 +85,5 @@ signature_generator.py
8485

8586
# binaries
8687
app/extensions/bin
88+
# geth binary download
89+
app/extensions/bin/geth*
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
3+
* You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
'use strict'
6+
7+
const settings = require('../../../js/constants/settings')
8+
const {configureEthWallet} = require('../../extensions')
9+
const appConstants = require('../../../js/constants/appConstants')
10+
const {makeImmutable} = require('../../common/state/immutableUtil')
11+
12+
const ethWalletReducer = (state, action, immutableAction) => {
13+
action = immutableAction || makeImmutable(action)
14+
15+
switch (action.get('actionType')) {
16+
case appConstants.APP_CHANGE_SETTING:
17+
{
18+
const key = action.get('key')
19+
const isEnabled = action.get('value')
20+
21+
if (isEnabled == null || key !== settings.ETHWALLET_ENABLED) {
22+
break
23+
}
24+
25+
configureEthWallet(isEnabled)
26+
break
27+
}
28+
}
29+
return state
30+
}
31+
32+
module.exports = ethWalletReducer

app/browser/tabs.js

+10
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,16 @@ const api = {
718718
}
719719
})
720720

721+
tab.on('did-detach', (e, oldTabId) => {
722+
// forget last active trail in window tab
723+
// is detaching from
724+
const oldTab = getTabValue(oldTabId)
725+
const detachedFromWindowId = oldTab ? oldTab.get('windowId') : undefined
726+
if (detachedFromWindowId != null) {
727+
activeTabHistory.clearTabFromWindow(detachedFromWindowId, oldTabId)
728+
}
729+
})
730+
721731
tab.on('did-attach', (e, tabId) => {
722732
// tab has been attached to a webview
723733
})

app/ethWallet-geth.js

+303
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
const fs = require('fs-extra')
2+
const path = require('path')
3+
const dns = require('dns-then')
4+
const {spawn, spawnSync} = require('child_process')
5+
const portfinder = require('portfinder')
6+
const net = require('net')
7+
const underscore = require('underscore')
8+
9+
const {app, ipcMain} = require('electron')
10+
const {getExtensionsPath} = require('../js/lib/appUrlUtil')
11+
const appStore = require('../js/stores/appStore')
12+
const ledgerState = require('./common/state/ledgerState')
13+
const {getSetting} = require('../js/settings')
14+
const settings = require('../js/constants/settings')
15+
const appDispatcher = require('../js/dispatcher/appDispatcher')
16+
const appConstants = require('../js/constants/appConstants')
17+
18+
const gethCache = process.env.GETH_CACHE || '1024'
19+
const envNet = process.env.ETHEREUM_NETWORK || 'mainnet'
20+
const envSubDomain = envNet === 'mainnet' ? 'ethwallet' : 'ethwallet-test'
21+
const gethDataDir = path.join(app.getPath('userData'), 'ethereum', envNet)
22+
23+
const isWindows = process.platform === 'win32'
24+
const gethProcessKey = isWindows ? 'geth.exe' : 'geth'
25+
26+
const ipcPath = isWindows ? '\\\\.\\pipe\\geth.ipc' : path.join(gethDataDir, 'geth.ipc')
27+
const pidPath = isWindows ? '\\\\.\\pipe\\geth.pid' : path.join(gethDataDir, 'geth.pid')
28+
const gethProcessPath = path.join(getExtensionsPath('bin'), gethProcessKey)
29+
30+
const configurePeers = async (dataDir) => {
31+
try {
32+
const discoveryDomain = `_enode._tcp.${envNet}.${envSubDomain}.brave.com`
33+
let newNodes = await dns.resolveSrv(discoveryDomain)
34+
newNodes = underscore.shuffle(newNodes).sort((a, b) => {
35+
const pdiff = a.priority - b.priority
36+
37+
return ((pdiff !== 0) ? pdiff : (b.weight - a.weight))
38+
})
39+
const newNodesNames = newNodes.map(({ name }) => name)
40+
41+
// start without await to take advantage of async parallelism
42+
const newNodesPublicKeysPromises = Promise.all(newNodesNames.map(name => dns.resolveTxt(name)))
43+
const newNodesIps = await Promise.all(newNodesNames.map(name => dns.resolve4(name)))
44+
const newNodesPublicKeys = await newNodesPublicKeysPromises
45+
46+
const enodes = newNodes.map(({name, port}, i) => `enode://${newNodesPublicKeys[i]}@${newNodesIps[i]}:${port}`)
47+
48+
await fs.writeFile(path.join(dataDir, 'geth', 'static-nodes.json'), JSON.stringify(enodes))
49+
} catch (e) {
50+
console.error('Failed to configure static nodes peers ' + e.message)
51+
}
52+
}
53+
54+
// needs to be shared to the eth-wallet app over ipc
55+
let wsPort
56+
// needs to be shared to the metamask extension
57+
let rpcPort
58+
59+
let geth
60+
let gethProcessId
61+
let gethRetryTimeoutId
62+
const gethRetryInterval = 30000
63+
64+
const spawnGeth = async () => {
65+
portfinder.basePort = 40400
66+
const port = await portfinder.getPortPromise()
67+
68+
portfinder.basePort = 40600
69+
wsPort = await portfinder.getPortPromise()
70+
71+
portfinder.basePort = 40800
72+
rpcPort = await portfinder.getPortPromise()
73+
74+
const gethArgs = [
75+
'--port',
76+
port,
77+
'--syncmode',
78+
'light',
79+
'--cache',
80+
gethCache,
81+
'--cache.database',
82+
gethCache,
83+
'--trie-cache-gens',
84+
gethCache,
85+
'--rpc',
86+
'--rpcport',
87+
rpcPort,
88+
'--ws',
89+
'--wsorigins',
90+
'chrome-extension://dakeiobolocmlkdebloniehpglcjkgcp',
91+
'--wsport',
92+
wsPort,
93+
'--datadir',
94+
gethDataDir,
95+
'--ipcpath',
96+
ipcPath,
97+
'--maxpeers',
98+
'10'
99+
]
100+
101+
if (envNet === 'ropsten') {
102+
gethArgs.push('--testnet')
103+
gethArgs.push('--rpcapi', 'admin,eth,web3')
104+
}
105+
106+
const gethOptions = {
107+
stdio: process.env.GETH_LOG ? 'inherit' : 'ignore'
108+
}
109+
110+
ensureGethDataDir()
111+
112+
// If the process from the previous browswer session still lingers, it should be killed
113+
if (await fs.pathExists(pidPath)) {
114+
try {
115+
const pid = await fs.readFile(pidPath)
116+
cleanupGeth(pid)
117+
} catch (ex) {
118+
console.error('Could not read from geth.pid')
119+
}
120+
}
121+
122+
geth = spawn(gethProcessPath, gethArgs, gethOptions)
123+
124+
geth.on('exit', handleGethStop.bind(null, 'exit'))
125+
geth.on('close', handleGethStop.bind(null, 'close'))
126+
127+
await writeGethPid(geth.pid)
128+
129+
console.warn('GETH: spawned')
130+
}
131+
132+
const ensureGethDataDir = () => {
133+
if (!isWindows) {
134+
fs.ensureDirSync(gethDataDir)
135+
} else {
136+
spawnSync('mkdir', ['-p', gethDataDir])
137+
}
138+
configurePeers(gethDataDir)
139+
}
140+
141+
const handleGethStop = (event, code, signal) => {
142+
console.warn(`GETH ${event}: Code: ${code} | Signal: ${signal}`)
143+
144+
if (code) {
145+
return
146+
}
147+
148+
const isEnabled = getSetting(settings.ETHWALLET_ENABLED)
149+
// Restart should occur on close only, else restart
150+
// events can compound.
151+
if (event === 'exit') {
152+
geth = null
153+
} else if (isEnabled && event === 'close') {
154+
restartGeth()
155+
}
156+
}
157+
158+
const writeGethPid = async (pid) => {
159+
if (!pid) {
160+
return
161+
}
162+
163+
gethProcessId = pid
164+
165+
try {
166+
await fs.writeFile(pidPath, gethProcessId)
167+
} catch (ex) {
168+
console.error('Could not write geth.pid')
169+
}
170+
}
171+
172+
const cleanupGeth = (processId) => {
173+
processId = processId || gethProcessId
174+
175+
if (processId) {
176+
// Set geth to null to remove bound listeners
177+
// Otherwise, geth will attempt to restart itself
178+
// when killed.
179+
geth = null
180+
181+
// Kill process
182+
process.kill(processId)
183+
184+
// Remove in memory process id
185+
gethProcessId = null
186+
187+
// Named pipes on Windows will get deleted
188+
// automatically once no processes are using them.
189+
if (!isWindows) {
190+
try {
191+
fs.unlinkSync(pidPath)
192+
} catch (ex) {
193+
console.error('Could not delete geth.pid')
194+
}
195+
}
196+
console.warn('GETH: cleanup done')
197+
}
198+
}
199+
200+
// Attempts to restart geth up to 3 times
201+
const restartGeth = async (tries = 3) => {
202+
if (tries === 0) {
203+
return
204+
}
205+
206+
await spawnGeth()
207+
208+
if (gethRetryTimeoutId) {
209+
clearTimeout(gethRetryTimeoutId)
210+
}
211+
212+
if (geth == null) {
213+
gethRetryTimeoutId = setTimeout(() => { restartGeth(--tries) }, gethRetryInterval)
214+
}
215+
}
216+
217+
// Geth should be killed on normal process, exit, SIGINT,
218+
// and application crashing exceptions.
219+
process.on('exit', () => {
220+
cleanupGeth(gethProcessId)
221+
})
222+
process.on('SIGINT', () => {
223+
cleanupGeth(gethProcessId)
224+
process.exit(2)
225+
})
226+
227+
ipcMain.on('eth-wallet-create-wallet', (e, pwd) => {
228+
const client = net.createConnection(ipcPath)
229+
230+
client.on('connect', () => {
231+
client.write(JSON.stringify({ 'method': 'personal_newAccount', 'params': [pwd], 'id': 1, 'jsonrpc': '2.0' }))
232+
})
233+
234+
client.on('data', (data) => {
235+
const res = JSON.parse(data.toString())
236+
if (res.result) {
237+
e.sender.send('eth-wallet-new-wallet', res.result)
238+
}
239+
client.end()
240+
})
241+
})
242+
243+
ipcMain.on('eth-wallet-wallets', (e, data) => {
244+
const client = net.createConnection(ipcPath)
245+
246+
client.on('connect', () => {
247+
client.write(JSON.stringify({ 'method': 'db_putString', 'params': ['braveEthWallet', 'wallets', data], 'id': 1, 'jsonrpc': '2.0' }))
248+
})
249+
250+
client.on('data', (data) => {
251+
client.end()
252+
})
253+
})
254+
255+
ipcMain.on('eth-wallet-unlock-account', (e, address, pw) => {
256+
const client = net.createConnection(ipcPath)
257+
258+
client.on('connect', () => {
259+
client.write(JSON.stringify({ 'method': 'personal_unlockAccount', 'params': [address, pw], 'id': 1, 'jsonrpc': '2.0' }))
260+
})
261+
262+
client.on('data', (data) => {
263+
client.end()
264+
e.sender.send('eth-wallet-unlock-account-result', data.toString())
265+
})
266+
})
267+
268+
ipcMain.on('eth-wallet-get-geth-address', (e) => {
269+
e.sender.send('eth-wallet-geth-address', `ws://localhost:${wsPort}`)
270+
})
271+
272+
ipcMain.on('get-popup-bat-balance', (e) => {
273+
const appState = appStore.getState()
274+
const ledgerInfo = ledgerState.getInfoProps(appState)
275+
e.sender.send('popup-bat-balance',
276+
ledgerInfo.get('balance'),
277+
ledgerInfo.getIn(['addresses', 'BAT']))
278+
})
279+
280+
ipcMain.on('eth-wallet-get-metamask-state', (e) => {
281+
e.sender.send('eth-wallet-metamask-state', getSetting(settings.METAMASK_ENABLED) ? 'enabled' : 'disabled')
282+
})
283+
284+
ipcMain.on('eth-wallet-enable-metamask', (e) => {
285+
appDispatcher.dispatch({
286+
actionType: appConstants.APP_CHANGE_SETTING,
287+
key: settings.METAMASK_ENABLED,
288+
value: true
289+
})
290+
})
291+
292+
ipcMain.on('eth-wallet-get-keys-path', (e) => {
293+
e.sender.send('eth-wallet-keys-path', path.join(gethDataDir, 'keystore'))
294+
})
295+
296+
const launchGeth = async function () {
297+
await spawnGeth()
298+
}
299+
300+
module.exports = {
301+
launchGeth,
302+
cleanupGeth
303+
}

0 commit comments

Comments
 (0)