trufflesuite / truffle

:warning: The Truffle Suite is being sunset. For information on ongoing support, migration options and FAQs, visit the Consensys blog. Thank you for all the support over the years.
https://consensys.io/blog/consensys-announces-the-sunset-of-truffle-and-ganache-and-new-hardhat?utm_source=github&utm_medium=referral&utm_campaign=2023_Sep_truffle-sunset-2023_announcement_
MIT License
14.03k stars 2.32k forks source link

Truffle Wallet Provider should support Websockets #2040

Closed barlock closed 4 years ago

barlock commented 5 years ago

Issue

The truffle-hdwallet-provider currently only supports http connections.

I'm using drizzle connected to Pantheon. For my test environment, I've spun it up so that none of my accounts need any eth to interact with the dapp. I like how your hooked wallet implementation can create ephemeral keys from a set mnemonic. It would be nice to be able to use that without forking.

Steps to Reproduce

Pass any WebSocket provider into truffle-hdwallet-provider and attach it to drizzle and it will tell you that subscriptions aren't supported. This is because the SubproviderProvider used in the engine doesn't support subscriptions. Any sub-provider that does, will be ignored.

Expected Behavior

Passing in a provider will yield the full feature set of the provider, not just being able to send a request.

Workaround

Right now, by editing the code to add a subprovider instead of creating a SubproviderProvider (phew) everything works as expected.

This could likely either be fixed by addressing what a SubProviderProvider can do, or by tweaking the initialization of your provider engine. The latter seemed better to me. I'd be happy to make a pr if you'll accept.

Example ```js const bip39 = require('bip39'); const ethJSWallet = require('ethereumjs-wallet'); const hdkey = require('ethereumjs-wallet/hdkey'); const ProviderEngine = require('web3-provider-engine'); const FiltersProvider = require('web3-provider-engine/subproviders/filters.js'); const WebsocketProvider = require('web3-provider-engine/subproviders/websocket.js'); const NonceProvider = require('web3-provider-engine/subproviders/nonce-tracker'); const HookedProvider = require('web3-provider-engine/subproviders/hooked-wallet.js'); const Transaction = require('ethereumjs-tx'); const ethUtil = require('ethereumjs-util'); export default (mnemonic, address_index = 0, num_addresses = 10) => { let hdwallet; const wallet_hdpath = "m/44'/60'/0'/0/"; const wallets = {}; const addresses = []; const engine = new ProviderEngine(); // private helper to normalize given mnemonic const normalizePrivateKeys = mnemonic => { if (Array.isArray(mnemonic)) return mnemonic; else if (mnemonic && !mnemonic.includes(' ')) return [mnemonic]; // if truthy, but no spaces in mnemonic else return false; // neither an array nor valid value passed; }; // private helper to check if given mnemonic uses BIP39 passphrase protection const checkBIP39Mnemonic = mnemonic => { hdwallet = hdkey.fromMasterSeed(bip39.mnemonicToSeed(mnemonic)); if (!bip39.validateMnemonic(mnemonic)) { throw new Error('Mnemonic invalid or undefined'); } // crank the addresses out for (let i = address_index; i < address_index + num_addresses; i++) { const wallet = hdwallet.derivePath(wallet_hdpath + i).getWallet(); const addr = `0x${wallet.getAddress().toString('hex')}`; addresses.push(addr); wallets[addr] = wallet; } }; // private helper leveraging ethUtils to populate wallets/addresses const ethUtilValidation = privateKeys => { // crank the addresses out for (let i = address_index; i < address_index + num_addresses; i++) { const privateKey = Buffer.from(privateKeys[i].replace('0x', ''), 'hex'); if (ethUtil.isValidPrivate(privateKey)) { const wallet = ethJSWallet.fromPrivateKey(privateKey); const address = wallet.getAddressString(); addresses.push(address); wallets[address] = wallet; } } }; const privateKeys = normalizePrivateKeys(mnemonic); if (!privateKeys) checkBIP39Mnemonic(mnemonic); else ethUtilValidation(privateKeys); const tmp_accounts = addresses; const tmp_wallets = wallets; engine.addProvider( new HookedProvider({ getAccounts(cb) { cb(null, tmp_accounts); }, getPrivateKey(address, cb) { if (!tmp_wallets[address]) { return cb('Account not found'); } else { cb(null, tmp_wallets[address].getPrivateKey().toString('hex')); } }, signTransaction(txParams, cb) { let pkey; const from = txParams.from.toLowerCase(); if (tmp_wallets[from]) { pkey = tmp_wallets[from].getPrivateKey(); } else { cb('Account not found'); } const tx = new Transaction(txParams); tx.sign(pkey); const rawTx = `0x${tx.serialize().toString('hex')}`; cb(null, rawTx); }, signMessage({ data, from }, cb) { const dataIfExists = data; if (!dataIfExists) { cb('No data to sign'); } if (!tmp_wallets[from]) { cb('Account not found'); } const pkey = tmp_wallets[from].getPrivateKey(); const dataBuff = ethUtil.toBuffer(dataIfExists); const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff); const sig = ethUtil.ecsign(msgHashBuff, pkey); const rpcSig = ethUtil.toRpcSig(sig.v, sig.r, sig.s); cb(null, rpcSig); }, signPersonalMessage(...args) { this.signMessage(...args); } }) ); engine.addProvider(new NonceProvider()); engine.addProvider(new FiltersProvider()); // Interesting bit is here engine.addProvider(new WebsocketProvider({ rpcUrl: 'ws://127.0.0.1:8546' })); engine.start(); // Required by the provider engine. return engine; }; ```

Environment

gnidan commented 5 years ago

I don't suppose you've tried adding websockets: true to your network config? See the networks configuration docs.

Thanks for raising this issue!

barlock commented 5 years ago

I haven't. I'm using the wallet provider in the browser with Drizzle. It doesn't ever see that config. I've wired Pantheon instead of ganache to use websockets.

I'll have a branch with a working example soon.

barlock commented 5 years ago

Here's the important line change https://github.com/ConsenSys/web3studio-sidejam/pull/15/files#diff-1166b1bc9915bf05db55080cfd04dcb1R121

Drizzle config: https://github.com/ConsenSys/web3studio-sidejam/pull/15/files#diff-f39bad30e90991680938d0737fb93d8dR10

Pantheon configuration: https://github.com/ConsenSys/web3studio-sidejam/pull/15/files#diff-4b6b1c9d03e80b998a3dc95e52f19aa6R4

eggplantzzz commented 4 years ago

Is this still an issue with the latest version of @truffle/hdwallet-provider?

gnidan commented 4 years ago

Closing this for issue maintenance since it appears that @truffle/hdwallet-provider does support websockets.

If there's still an issue, well, let us know, and we'll be happy to re-open! Thanks y'all 🙇