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

Eth-wallet rebased #14734

Merged
merged 93 commits into from
Aug 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
2f69e74
[WIP] eth wallet support
diracdeltas Feb 17, 2018
b4ab64b
geth runs when wallet is enabled
jumde Feb 17, 2018
73c7dbf
move UI into a browserAction
diracdeltas Feb 17, 2018
899eb72
Add tooling for upgrading geth, bundling
evq Feb 17, 2018
745ceb8
add ETHEREUM_NETWORK env var to launch geth with --testnet
evq Feb 17, 2018
8160f1f
add buttons to browserAction
diracdeltas Feb 17, 2018
26dc7c3
separate geth download from tools/downloadEthwallet
diracdeltas Feb 17, 2018
95b7170
update meteor-dapp-wallet-prebuilt
diracdeltas Feb 17, 2018
d489a61
remove ethwallet about: url mapping
diracdeltas Feb 17, 2018
9670596
remove unused fs require
diracdeltas Feb 17, 2018
d41a694
Fix ethereum wallet title display in titleMode
diracdeltas Feb 17, 2018
2850d0f
WIP: adding BAT balance display
diracdeltas Feb 18, 2018
caa4f6b
fixup geth downloader, only dl once plus updates, change bin path
evq Feb 18, 2018
98fd603
add hacky way to load transfer funds URL
diracdeltas Feb 18, 2018
e034b88
resized ethereum logo
jumde Feb 18, 2018
60052a8
make transfer funds button slightly less hacky
diracdeltas Feb 18, 2018
e8134ef
remove BAT balance for now
diracdeltas Feb 18, 2018
368a93c
resolving conflicts
jumde Feb 18, 2018
6958d7f
add bat contract on first load
evq Feb 18, 2018
176fc16
More style fixes, simplify create-wallet IPC
diracdeltas Feb 18, 2018
5b465ea
use new geth binary, WIP read geth pw from stdin
diracdeltas Feb 18, 2018
c0bb60b
open ethwallet tab when wallet creation is done
diracdeltas Feb 18, 2018
e0ed506
switch to using ipc directly vs shelling out to get for account creation
evq Feb 18, 2018
bf5f32e
pass ipcath explicitly
evq Feb 18, 2018
0dd0297
UI tweaks
diracdeltas Feb 18, 2018
028797c
update Geth to 1.8.12
evq Feb 21, 2018
ed26efa
unsafe-inline is ignored by chrome in extensions
Slava Jul 13, 2018
d983edb
switch geth to use WS interface
Slava Jul 13, 2018
aa43cfe
browser context menu changes for eth wallet
Slava Jul 17, 2018
ea5ace9
Embed eth-wallet into preferences panel
Slava Jul 19, 2018
41ecbc5
save wallet names into geth
Slava Jul 20, 2018
a762df9
connect ethwallet switch to actual settings
Slava Jul 20, 2018
b7d2d79
Use a named pipe for windows
Slava Jul 24, 2018
80fe5db
add an option to print logs of geth
Slava Jul 25, 2018
1831fd3
add testnet static node
Slava Jul 25, 2018
8cb854d
support for unlocking and creating accounts from ui
Slava Jul 25, 2018
86f2c46
hide eth wallet extension in about:preferences#extensions
Slava Jul 26, 2018
28063f6
add another test node
Slava Jul 26, 2018
27f3fe0
expose extra apis in on testnet
Slava Jul 26, 2018
7ca66e9
template for disabled text
Slava Jul 26, 2018
3fe408d
style the info page of eth-wallet
Slava Jul 26, 2018
abdce2b
pre-emptively create the geth folder
Slava Jul 31, 2018
c0f022b
Remove unused eth-wallet files
Slava Jul 31, 2018
478f3eb
add metamask icon for disabled content page
cezaraugusto Jul 31, 2018
b89c75c
Making attempts to reboot Geth on process exit
ryanml Jul 31, 2018
2c1c273
add icon for ethwallet prefs sidebar
cezaraugusto Aug 1, 2018
62742b2
Handling Geth on process exit events, restart
ryanml Aug 1, 2018
4bf783a
Generic handler for exit/close events
ryanml Aug 1, 2018
36e35cf
use separate datadir depending on the net
Slava Aug 1, 2018
1727b62
replace static-nodes.json list with a dynamic list queried from DNS
Slava Aug 1, 2018
b41f485
placeholder domain for mainnet, syncmode arg
Slava Aug 1, 2018
cfb127c
randomize ports, extract geth management into a separate file
Slava Aug 2, 2018
7b29237
switch back to static-nodes.json
Slava Aug 2, 2018
e795bb4
generate an rpcPort
Slava Aug 2, 2018
c781c47
reduce the number of peers in an attempt to fix #14882
Slava Aug 2, 2018
c4e75a1
declare notifications permimssions for #14862
Slava Aug 2, 2018
bf0a030
add error handling to static nodes code
Slava Aug 2, 2018
9c9c76a
ethWallet locale name in english
Slava Aug 2, 2018
6dde942
reposition overlay and iframe
Slava Aug 2, 2018
1b40d47
CSS scrollbar hacks for iframe
Slava Aug 2, 2018
fa4552c
ui improvements for the panel
Slava Aug 3, 2018
9360fc5
Fixing get restart
ryanml Aug 3, 2018
2c49e26
Fixing eth-wallet button, formatting
ryanml Aug 3, 2018
3e9029f
Upgrading Geth to 1.8.13, --cache -> 1024
ryanml Aug 3, 2018
8481776
revert ui changes
Slava Aug 3, 2018
812663f
make another folder for each net
Slava Aug 4, 2018
b86d9c4
Marshal's patch1
Slava Aug 4, 2018
5168abd
Adding DB/trie cache args, fixing DNS addresses
ryanml Aug 6, 2018
13431f6
Allowing Windows to delete named pipes on its own
ryanml Aug 6, 2018
ea63f3c
Ensuring data path before write
ryanml Aug 7, 2018
3081293
Passing --nousb to disable usb monitoring notices
ryanml Aug 7, 2018
81b2ff6
Ensure iframe container never has scrollbars
Slava Aug 6, 2018
87bb9e0
new password flow
Slava Aug 7, 2018
9db01a0
autocomplete=off on inputs
Slava Aug 7, 2018
789557c
Updating package lock
ryanml Aug 7, 2018
a24c748
spinner ui
Slava Aug 7, 2018
467722a
Removing --nousb arg
ryanml Aug 7, 2018
3a27ad1
significant digits on total balance
Slava Aug 7, 2018
d6511eb
Allowing ethWallet to be enabled/disabled without restart
ryanml Aug 8, 2018
585dd5f
Reload transition adjustments
ryanml Aug 9, 2018
fe5f692
Using mkdir -p for Windows
ryanml Aug 9, 2018
4779310
instant wallet creation in ui
Slava Aug 9, 2018
3fbdbd5
Using spawnSync
ryanml Aug 9, 2018
ac7b1bc
metamask button interactions
Slava Aug 9, 2018
537b436
additional text on eth wallet feature desc
Slava Aug 10, 2018
91b7680
message about backing up keys
Slava Aug 10, 2018
e4e1bac
remove add contract button
Slava Aug 10, 2018
c76812a
fixing settings persistance
ryanml Aug 10, 2018
c623dd5
write static-nodes.json to the right location
Slava Aug 10, 2018
be48f37
wallet creation tweaks
Slava Aug 11, 2018
b36ab69
Making ensureGethDataDir synchronous, removing unecessary call
ryanml Aug 13, 2018
989a6c2
Moving configurePeers
ryanml Aug 14, 2018
64f6897
on uncaught removal
ryanml Aug 14, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Brave.tar.bz2
app/extensions/gen
app/extensions/brave/gen
app/extensions/torrent/gen
app/extensions/ethwallet
*.pfx
js/constants/buildConfig.js

Expand All @@ -84,3 +85,5 @@ signature_generator.py

# binaries
app/extensions/bin
# geth binary download
app/extensions/bin/geth*
32 changes: 32 additions & 0 deletions app/browser/reducers/ethWalletReducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict'

const settings = require('../../../js/constants/settings')
const {configureEthWallet} = require('../../extensions')
const appConstants = require('../../../js/constants/appConstants')
const {makeImmutable} = require('../../common/state/immutableUtil')

const ethWalletReducer = (state, action, immutableAction) => {
action = immutableAction || makeImmutable(action)

switch (action.get('actionType')) {
case appConstants.APP_CHANGE_SETTING:
{
const key = action.get('key')
const isEnabled = action.get('value')

if (isEnabled == null || key !== settings.ETHWALLET_ENABLED) {
break
}

configureEthWallet(isEnabled)
break
}
}
return state
}

module.exports = ethWalletReducer
10 changes: 10 additions & 0 deletions app/browser/tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,16 @@ const api = {
}
})

tab.on('did-detach', (e, oldTabId) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Slava is this block supposed to be here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why this is needed. Maybe you can remember the context of this commit? c375ba3

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Slava lol wow i have no idea. sorry i thought it was left over from the rebase.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

er yeah this needs to - looks like a rebase issue from before -> after the signle-webview project. Tab's now detach every time they are made inactive / active, so this will break something (minor).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@petemill sorry do you mean this chunk should be deleted or kept (or other)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@petemill ping

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like this block got added during rebase because of a fix within in the original ethwallet branch - 00cdfdd#diff-2be38b3d97f35cd818780bac4be979c4R629

I think we should delete this block

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops missed out a key word

this needs to go

Thanks for the spot

// forget last active trail in window tab
// is detaching from
const oldTab = getTabValue(oldTabId)
const detachedFromWindowId = oldTab ? oldTab.get('windowId') : undefined
if (detachedFromWindowId != null) {
activeTabHistory.clearTabFromWindow(detachedFromWindowId, oldTabId)
}
})

tab.on('did-attach', (e, tabId) => {
// tab has been attached to a webview
})
Expand Down
303 changes: 303 additions & 0 deletions app/ethWallet-geth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
const fs = require('fs-extra')
const path = require('path')
const dns = require('dns-then')
const {spawn, spawnSync} = require('child_process')
const portfinder = require('portfinder')
const net = require('net')
const underscore = require('underscore')

const {app, ipcMain} = require('electron')
const {getExtensionsPath} = require('../js/lib/appUrlUtil')
const appStore = require('../js/stores/appStore')
const ledgerState = require('./common/state/ledgerState')
const {getSetting} = require('../js/settings')
const settings = require('../js/constants/settings')
const appDispatcher = require('../js/dispatcher/appDispatcher')
const appConstants = require('../js/constants/appConstants')

const gethCache = process.env.GETH_CACHE || '1024'
const envNet = process.env.ETHEREUM_NETWORK || 'mainnet'
const envSubDomain = envNet === 'mainnet' ? 'ethwallet' : 'ethwallet-test'
const gethDataDir = path.join(app.getPath('userData'), 'ethereum', envNet)

const isWindows = process.platform === 'win32'
const gethProcessKey = isWindows ? 'geth.exe' : 'geth'

const ipcPath = isWindows ? '\\\\.\\pipe\\geth.ipc' : path.join(gethDataDir, 'geth.ipc')
const pidPath = isWindows ? '\\\\.\\pipe\\geth.pid' : path.join(gethDataDir, 'geth.pid')
const gethProcessPath = path.join(getExtensionsPath('bin'), gethProcessKey)

const configurePeers = async (dataDir) => {
try {
const discoveryDomain = `_enode._tcp.${envNet}.${envSubDomain}.brave.com`
let newNodes = await dns.resolveSrv(discoveryDomain)
newNodes = underscore.shuffle(newNodes).sort((a, b) => {
const pdiff = a.priority - b.priority

return ((pdiff !== 0) ? pdiff : (b.weight - a.weight))
})
const newNodesNames = newNodes.map(({ name }) => name)

// start without await to take advantage of async parallelism
const newNodesPublicKeysPromises = Promise.all(newNodesNames.map(name => dns.resolveTxt(name)))
const newNodesIps = await Promise.all(newNodesNames.map(name => dns.resolve4(name)))
const newNodesPublicKeys = await newNodesPublicKeysPromises

const enodes = newNodes.map(({name, port}, i) => `enode://${newNodesPublicKeys[i]}@${newNodesIps[i]}:${port}`)

await fs.writeFile(path.join(dataDir, 'geth', 'static-nodes.json'), JSON.stringify(enodes))
} catch (e) {
console.error('Failed to configure static nodes peers ' + e.message)
}
}

// needs to be shared to the eth-wallet app over ipc
let wsPort
// needs to be shared to the metamask extension
let rpcPort

let geth
let gethProcessId
let gethRetryTimeoutId
const gethRetryInterval = 30000

const spawnGeth = async () => {
portfinder.basePort = 40400
const port = await portfinder.getPortPromise()

portfinder.basePort = 40600
wsPort = await portfinder.getPortPromise()

portfinder.basePort = 40800
rpcPort = await portfinder.getPortPromise()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do either the RPC or websocket ports accept connections from localhost or other whitelisted origins (not counting chrome-extension://...)? if so we need to make sure the RPC and websocket ports are not vulnerable to DNS rebinding: transmission/transmission#468

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the solution is to have the listening service validate the incoming request's host header before doing any processing on it. here is an example for webtorrent: https://github.com/webtorrent/webtorrent/pull/1260/files

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would setting the following options be sufficient?

  --rpccorsdomain value  Comma separated list of domains from which to accept cross origin requests (browser enforced)
  --rpcvhosts value      Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. (default: "localhost")

We should set these after we get more concrete metamask integration?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Slava CORS is unrelated. the latter looks like it should do the right thing but i haven't audited the code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--rpcvhosts does the right thing for HTTP, but I don't think it does the right thing for WS. looking into it more...

https://github.com/ethereum/go-ethereum/blob/5d7e18539e32cb4f1aafab8e977e28a7cd34da9c/rpc/http.go#L314

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah that flag only applies to HTTP-RPC. the Geth WS-RPC doesn't actually have a way to enforce checks on the Host header.

but it looks like there are no additional whitelisted origins (for WS-RPC) added beyond those passed via --wsorigins, and in our case we're only passing chrome-extension://...

so I think we are okay as we continue to pass --wsorigins for WS-RPC and add --rpcvhosts for HTTP-RPC

const gethArgs = [
'--port',
port,
'--syncmode',
'light',
'--cache',
gethCache,
'--cache.database',
gethCache,
'--trie-cache-gens',
gethCache,
'--rpc',
'--rpcport',
rpcPort,
'--ws',
'--wsorigins',
'chrome-extension://dakeiobolocmlkdebloniehpglcjkgcp',
'--wsport',
wsPort,
'--datadir',
gethDataDir,
'--ipcpath',
ipcPath,
'--maxpeers',
'10'
]

if (envNet === 'ropsten') {
gethArgs.push('--testnet')
gethArgs.push('--rpcapi', 'admin,eth,web3')
}

const gethOptions = {
stdio: process.env.GETH_LOG ? 'inherit' : 'ignore'
}

ensureGethDataDir()

// If the process from the previous browswer session still lingers, it should be killed
if (await fs.pathExists(pidPath)) {
try {
const pid = await fs.readFile(pidPath)
cleanupGeth(pid)
} catch (ex) {
console.error('Could not read from geth.pid')
}
}

geth = spawn(gethProcessPath, gethArgs, gethOptions)

geth.on('exit', handleGethStop.bind(null, 'exit'))
geth.on('close', handleGethStop.bind(null, 'close'))

await writeGethPid(geth.pid)

console.warn('GETH: spawned')
}

const ensureGethDataDir = () => {
if (!isWindows) {
fs.ensureDirSync(gethDataDir)
} else {
spawnSync('mkdir', ['-p', gethDataDir])
}
configurePeers(gethDataDir)
}

const handleGethStop = (event, code, signal) => {
console.warn(`GETH ${event}: Code: ${code} | Signal: ${signal}`)

if (code) {
return
}

const isEnabled = getSetting(settings.ETHWALLET_ENABLED)
// Restart should occur on close only, else restart
// events can compound.
if (event === 'exit') {
geth = null
} else if (isEnabled && event === 'close') {
restartGeth()
}
}

const writeGethPid = async (pid) => {
if (!pid) {
return
}

gethProcessId = pid

try {
await fs.writeFile(pidPath, gethProcessId)
} catch (ex) {
console.error('Could not write geth.pid')
}
}

const cleanupGeth = (processId) => {
processId = processId || gethProcessId

if (processId) {
// Set geth to null to remove bound listeners
// Otherwise, geth will attempt to restart itself
// when killed.
geth = null

// Kill process
process.kill(processId)

// Remove in memory process id
gethProcessId = null

// Named pipes on Windows will get deleted
// automatically once no processes are using them.
if (!isWindows) {
try {
fs.unlinkSync(pidPath)
} catch (ex) {
console.error('Could not delete geth.pid')
}
}
console.warn('GETH: cleanup done')
}
}

// Attempts to restart geth up to 3 times
const restartGeth = async (tries = 3) => {
if (tries === 0) {
return
}

await spawnGeth()

if (gethRetryTimeoutId) {
clearTimeout(gethRetryTimeoutId)
}

if (geth == null) {
gethRetryTimeoutId = setTimeout(() => { restartGeth(--tries) }, gethRetryInterval)
}
}

// Geth should be killed on normal process, exit, SIGINT,
// and application crashing exceptions.
process.on('exit', () => {
cleanupGeth(gethProcessId)
})
process.on('SIGINT', () => {
cleanupGeth(gethProcessId)
process.exit(2)
})

ipcMain.on('eth-wallet-create-wallet', (e, pwd) => {
const client = net.createConnection(ipcPath)

client.on('connect', () => {
client.write(JSON.stringify({ 'method': 'personal_newAccount', 'params': [pwd], 'id': 1, 'jsonrpc': '2.0' }))
})

client.on('data', (data) => {
const res = JSON.parse(data.toString())
if (res.result) {
e.sender.send('eth-wallet-new-wallet', res.result)
}
client.end()
})
})

ipcMain.on('eth-wallet-wallets', (e, data) => {
const client = net.createConnection(ipcPath)

client.on('connect', () => {
client.write(JSON.stringify({ 'method': 'db_putString', 'params': ['braveEthWallet', 'wallets', data], 'id': 1, 'jsonrpc': '2.0' }))
})

client.on('data', (data) => {
client.end()
})
})

ipcMain.on('eth-wallet-unlock-account', (e, address, pw) => {
const client = net.createConnection(ipcPath)

client.on('connect', () => {
client.write(JSON.stringify({ 'method': 'personal_unlockAccount', 'params': [address, pw], 'id': 1, 'jsonrpc': '2.0' }))
})

client.on('data', (data) => {
client.end()
e.sender.send('eth-wallet-unlock-account-result', data.toString())
})
})

ipcMain.on('eth-wallet-get-geth-address', (e) => {
e.sender.send('eth-wallet-geth-address', `ws://localhost:${wsPort}`)
})

ipcMain.on('get-popup-bat-balance', (e) => {
const appState = appStore.getState()
const ledgerInfo = ledgerState.getInfoProps(appState)
e.sender.send('popup-bat-balance',
ledgerInfo.get('balance'),
ledgerInfo.getIn(['addresses', 'BAT']))
})

ipcMain.on('eth-wallet-get-metamask-state', (e) => {
e.sender.send('eth-wallet-metamask-state', getSetting(settings.METAMASK_ENABLED) ? 'enabled' : 'disabled')
})

ipcMain.on('eth-wallet-enable-metamask', (e) => {
appDispatcher.dispatch({
actionType: appConstants.APP_CHANGE_SETTING,
key: settings.METAMASK_ENABLED,
value: true
})
})

ipcMain.on('eth-wallet-get-keys-path', (e) => {
e.sender.send('eth-wallet-keys-path', path.join(gethDataDir, 'keystore'))
})

const launchGeth = async function () {
await spawnGeth()
}

module.exports = {
launchGeth,
cleanupGeth
}
Loading