diff --git a/README.md b/README.md index 925ac47a62946234e38eb9e88edb2c785509eb06..4f2ea6030f72ac683edbf89313d5493778db35c5 100644 --- a/README.md +++ b/README.md @@ -12,23 +12,26 @@ Esta guÃa deberÃa funcionar en Debian o sus derivados. Testeado en *Debian* y - como root: `apt install git` 2. Cloná el repositorio oficial BFA - `git clone https://gitlab.bfa.ar/blockchain/nucleo.git bfa` -3. Ejecutá el script de instalación. Esto cambiará algunas configuraciones en tu sistema. Si te preocupa (¿deberÃa?), podés ejecutar este paso manualmente. +3. Ejecutá el script de instalación. Esto cambiará algunas configuraciones en tu sistema. Si te preocupa (¿deberÃa?), podés ejecutar este escript paso a paso manualmente. - como root: `bfa/bin/installbfa.sh` - Van a aparecer varios *warnings* mientras se instala web3. Esto parece ser "normal". Ignorarlo no parece causar problemas. + Van a aparecer varios **warnings** mientras se instala web3. Esto parece ser "normal". Ignorarlo no parece causar problemas. 4. Cambiá al usuario `bfa` - como root: `su - bfa` -5. Comenzá la sincronización. Esto puede llevar un rato largo (este script se ejecuta automáticamente cuando se reinicia el sistema). +5. Comenzá la sincronización. **Esto puede llevar un rato largo** (este script se ejecuta automáticamente cuando se reinicia el sistema). - como bfa: `start.sh` 6. Monitoreá los logs con `bfalog.sh`. Apretá CTRL-C en cualquier momento para detener el `tail -f`. 7. Cambiá la configuración de tu nodo usando `admin.sh syncmode` - Hacé esto antes de haber sincronizado mucho en el paso anterior, ya que esto podrÃa remover todos los datos de la cadena que hayas bajado y reiniciar la sincronización de la cadena. 8. Esperá a aque termine de sincronizar -9. Ejecutá `maymine.sh` para actualizar tu configuración (detecta si estás autorizado a sellar/minar o no). Podés ejecutar esto todas las veces que quieras. Si tratás de sellar y no tenés permiso, van a aparecer errores en el log (pero no se rompe nada). -10. Herramientas simples super básicas (más bien pruebas de concepto, para inspirar a los programadores): +9. Herramientas simples super básicas (más bien pruebas de concepto, para inspirar a los programadores): - `explorer.sh` : Sigue el bloque más nuevo "*lastest*" por default, pero podés especificar un número de bloque cualquiera como argumento, por ejemplo `explorer.sh 0` permite ver el génesis (bloque 0). - `walker.pl` : También toma un número de bloque para iniciar. Sigue esperando nuevos bloques. -Hay otros programas "interesantes" en los directorios `bin/` y `src/`, pero para los desarrolladores, el branch `dev` es más intersante. +Hay otros programas "interesantes" en los directorios `bin/` y `src/`, +pero para los desarrolladores, el branch `dev` es más intersante y tambien el +([repositorio contrib](https://gitlab.bfa.ar/blockchain/contrib)). + +**Puede tardarse alrededor de una hora conectarse la primera vez. En el log no se ve nada. Hay que tener paciencia.** ## start.sh @@ -84,7 +87,14 @@ Cuando un operador de un sellador vota para promover o remover un sellador, este script hace la mayor parte. Toma un solo argumento, el número de la cuenta del sellador por el cual se vota. -## monitor.sh +## unlock.js + +Debloqua las cuentas del sistema (vease tambien `monitor.js`). Si una cuenta +tiene clave, se puede poner la clave con este script. + +## monitor.js -Esto se corre cada minuto desde `cron.sh`. Actualizará el un archivo -en `network/status` que muestra información de la red muy básica. +Esto se corre desde `cron.sh`. Cada minuto actualizará el archivo +`network/status` que muestra información del estado del nodo muy básica. +Tambien habilita sellar/minar si la cuenta (eth.accounts[0]) esta permitido +segun la red. Desbloqua cuentas si no tienen passwords. \ No newline at end of file diff --git a/bin/MasterDistiller.js b/bin/MasterDistiller.js index c7deec68f64b8d9ce450fe7e760356a978c66845..b43cc1f65f2c9d25a7c9e376dbf3d1715ca45258 100755 --- a/bin/MasterDistiller.js +++ b/bin/MasterDistiller.js @@ -9,7 +9,7 @@ const rl = require('readline').createInterface( var web3; var Distillery; var bfa; -var notation = [ 6 ]; +var notation = [ 15 ]; function init() { @@ -21,14 +21,36 @@ function init() case 9: notation.push( "Gwei" ); break; + case 12: + notation.push( "micro" ); + break; + case 15: + notation.push( "finney" ); + break; + case 18: + notation.push( "ether" ); + break; + case 21: + notation.push( "kether" ); + break; + case 24: + notation.push( "grand" ); + break; + case 27: + notation.push( "mether" ); + break; + case 30: + notation.push( "gether" ); + break; + case 33: + notation.push( "tether" ); + break; default: - notation = [ 6, Math.pow(10, 6), "Mwei" ]; + notation = [ 15, Math.pow(10, 15), "finney" ]; } bfa = new Libbfa(); web3 = bfa.newweb3(); Distillery = bfa.contract( web3, 'Distillery' ); - if ( undefined == Distillery ) - fatal('Can not initialise Distillery contact.'); web3.eth.getBalance( Distillery.contractaddress ).then( function receivedOwnBalance(val) { Distillery.contractbalance = val; diff --git a/bin/bfaupdate.sh b/bin/bfaupdate.sh new file mode 100644 index 0000000000000000000000000000000000000000..f6109cc0f79cc5b3572b8a89bbd1dd559285c4dd --- /dev/null +++ b/bin/bfaupdate.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# Robert Martin-Legene <robert@nic.ar> + +if [ -z "${BFAHOME}" ]; then echo "\$BFAHOME not set. Did you source bfa/bin/env ?" >&2; exit 1; fi +source ${BFAHOME}/bin/libbfa.sh || exit 1 + +set -x +cd ${BFAHOME} +git pull +npm rebuild diff --git a/bin/cron.sh b/bin/cron.sh index 50a0b0ad669253b9e806284b01d57aa52916ad94..6710a967f89f623893b4c7dd33c5ef8b494af880 100755 --- a/bin/cron.sh +++ b/bin/cron.sh @@ -1,10 +1,8 @@ #!/bin/bash trap 'exit 1' ERR -# Go $HOME -cd # Log in home directory -exec < /dev/null > bfa-cron-output.log 2>&1 +exec < /dev/null > ${HOME}/bfa-cron-output.log 2>&1 # Go to script bin, 'cause we expect to find $BFAHOME/bin/env there cd `dirname $0` source ./env diff --git a/bin/libbfa.js b/bin/libbfa.js index d0bf4832f6d4dc868d467ebfc55f95214a363cc7..2146b270a883e80d45416b7f560c2962d4ac6f78 100644 --- a/bin/libbfa.js +++ b/bin/libbfa.js @@ -52,16 +52,16 @@ module.exports = class Libbfa { var contractdir = [ this.networkdir, 'contracts', name ].join('/'); if ( ! this.fs.existsSync( contractdir ) ) - return; + this.fatal( 'The directory containing this contract ("' + contractdir + '") does not exist.' ); var contractaddress = this.fs.realpathSync( contractdir ).replace(/^.*\//, ''); if ( undefined == contractaddress ) - return; + this.fatal( 'Contract address for "' + name + '" not found.' ); var abistr = this.fs.readFileSync( contractdir + '/abi' ).toString(); if ( undefined == abistr ) - return; + this.fatal( 'ABI for contract "' + name + '" not found' ); var abi = JSON.parse( abistr ); if ( undefined == abi ) - return; + this.fatal( 'Failed to convert ABI for contract "' + name + '" into JSON.' ); var c = new w3.eth.Contract( abi, contractaddress ); c.abi = abi; c.contractaddress = contractaddress; @@ -81,32 +81,46 @@ module.exports = class Libbfa w3.eth.extend({ //property: 'bfaclique', methods: [{ - name: 'getSigners', + name: 'bfaGetSigners', call: 'clique_getSigners', params: 0 }] }); w3.eth.extend({ methods: [{ - name: 'minerstart', + name: 'bfaMinerstart', call: 'miner_start', params: 0 }] }); w3.eth.extend({ methods: [{ - name: 'minerstop', + name: 'bfaMinerstop', call: 'miner_stop', params: 0 }] }); w3.eth.extend({ methods: [{ - name: 'adminpeers', + name: 'bfaAdminpeers', call: 'admin_peers', params: 0 }] }); + w3.eth.extend({ + methods: [{ + name: 'bfaAdminaddPeer', + call: 'admin_addPeer', + params: 1 + }] + }); + w3.eth.personal.extend({ + methods: [{ + name: 'bfalistWallets', + call: 'personal_listWallets', + params: 0 + }] + }); if ( undefined != process.env.BFAACCOUNT ) { w3.eth.defaultAccount = this.account; } diff --git a/bin/monitor.js b/bin/monitor.js index 0d5eaa0331bc5bce0b61f19ce673992bd296529c..2838293a4437fd92aa98d2c65c7b746eb46762ab 100755 --- a/bin/monitor.js +++ b/bin/monitor.js @@ -5,41 +5,207 @@ "use strict" const Libbfa = require( process.env.BFAHOME + '/bin/libbfa.js'); +var bfa = new Libbfa(); +var web3 = bfa.newweb3(); +var lastUnlock = 0; -function monitor() +function peerlist() { - var bfa = new Libbfa(); - var web3 = bfa.newweb3(); - var now = new Date(); - //console.log(web3.eth); - web3.eth.adminpeers().then( - function(nodelist) { - var peers = []; + web3.eth.bfaAdminpeers().then( + function gotAdminPeers( nodelist ) { + var now = new Date(); + var nowpeers = []; + var peers = []; + var newpeers = []; + if ( bfa.fs.existsSync( bfa.networkdir + '/peers.cache' ) ) + { + var data = bfa.fs.readFileSync( bfa.networkdir + '/peers.cache' ).toString(); + if ( data.length > 0 ) + peers = data.split(/\r?\n/); + var i = peers.length; + // for some odd reason, I keep seeing empty entries + while ( i-- > 0 ) + if ( peers[i] == '' ) + peers.splice(i,1); + } nodelist.forEach( function(node) { - if ( typeof(node.protocols.eth) == 'object' ) - { - var dir = "out"; - if ( node.network.inbound ) - dir = "in"; - peers.push( "peer "+dir+": "+node.enode ); + if ( 'object' == typeof(node.protocols.eth) ) + { + // default info - likely to get overwritten. + var info = "<" + node.id + ">"; + var dir = ""; + if ( undefined != node.network ) + { + if ( 'boolean' == typeof(node.network.inbound) ) + { + if ( node.network.inbound ) + dir = "in"; + else + dir = "out"; + } + if ( 'string' == typeof(node.enode) ) + { + info = node.enode; + if (peers.indexOf( node.enode ) == -1 && dir == "out" ) + newpeers.push( node.enode ); + } + else + { + info = ""; + if ( undefined != node.id ) + info += "<" + node.id + ">"; + if ( undefined != node.network.remoteAddress ) + { + if ( info != "" ) + info+= "@"; + info += node.network.remoteAddress; + } + } + } + nowpeers.push( "peer " + dir + ": " + info ); } } ); + // write network/status bfa.fs.writeFileSync( bfa.networkdir + '/status', "UTC: " + now.toUTCString() + "\n" - + "BFA peers: " + peers.length + "\n" - + peers.sort().join("\n") + "\n", + + "BFA peers: " + nowpeers.length + "\n" + + nowpeers.sort().join("\n") + "\n", { mode: 0o644 } ); + // Try to connect to a random node if we have very few peers + if ( nowpeers.length < 5 && peers.length > 0 ) + { + var i = Math.floor( Math.random() * peers.length ); + var enode = peers[i]; + console.log( + "We have " + + nowpeers.length + + " peer" + ( nowpeers.length==1 ? '' : 's' ) + ", so will try to connect to " + + enode + ); + web3.eth.bfaAdminaddPeer( enode ); + } + // write network/peers.cache + // peers.cache is a list of peers we have connected out to in the past. + peers = peers.concat( newpeers ); + if (peers.length > 100) + peers.splice( 0, peers.length - 100 ); + bfa.fs.writeFileSync( + bfa.networkdir + '/peers.cache', + peers.join("\n") + "\n", + { mode: 0o644 } + ); + }, + function failedToGetAdminPeers(x) + { + // ignore connection problems? + } + ); +} + +// Function to determine if our defaultAccount is allowed to seal/mine. +// It will adjust the behaviour accordingly, i.e. stop or start mining. +function mayseal() +{ + var me = web3.eth.defaultAccount; + if ( undefined == me ) + { + console.log( "Failed to get default account information." ); + return; + } + me = me.toLowerCase(); + web3.eth.isMining(). + then( + // returns a boolean whether or not we are currently mining/sealing. + function( isMining ) + { + // Get a list of clique.getSigners, so we can see if we are + // in the list of authorized sealers. + web3.eth.bfaGetSigners() + .then( + function gotListOfSealers(x) + { + var lcsealers = x.map( name => name.toLowerCase() ); + var isSigner = (lcsealers.indexOf(me) > -1); + if ( isSigner ) + { + if ( ! isMining ) + { + console.log( 'Started to seal.' ); + web3.eth.bfaMinerstart(); + } + } + else + { + if ( isMining ) + { + console.log( 'I was trying to seal, but am not authorized. Stopped trying.' ); + web3.eth.bfaMinerstop(); + } + } + }, + function failedToGetListOfSealers(x) + { + console.log(x); + } + ); }, - function(x) + function failedToGetIsMiningBool(x) { - console.log( x ); - process.exit(1) + // Probably geth is not running. + //console.log(x); } ); } -monitor(); +function unlock() +{ + if ( lastUnlock + 600 > Date.now() / 1000 ) + return; + web3.eth.personal.bfalistWallets() + .then( + function pushone(x) + { + var i = x.length; + var wallets = new Array(); + while ( i-- > 0 ) + if ( x[i].status == "Locked" ) + wallets.push( x[i] ); + i = wallets.length; + if ( i == 0 ) + return; + var promises = new Array(); + while ( i-- > 0 ) + { + var j = wallets[i].accounts.length; + while ( j-- > 0 ) + { + var addr = wallets[i].accounts[j].address; + var promise = + web3.eth.personal.unlockAccount( addr, "", 0 ) + .catch( error => { } ); + promises.push( promise ); + } + } + lastUnlock = Date.now() / 1000; + } + , + function err(x) + { + // we don't care? + } + ) +} + +function timer() +{ + peerlist(); + mayseal(); + unlock(); +} + +setTimeout( timer, 5 * 1000 ); +setInterval( timer, 60 * 1000 ); diff --git a/bin/start.sh b/bin/start.sh index 74a395df2a735ce9cdc40414fafdba5ac18a9ac6..38d4f4b9f028e0c936ed93cf3f102e00b17cf4ea 100755 --- a/bin/start.sh +++ b/bin/start.sh @@ -14,39 +14,11 @@ enodeDGSI="59ae768ecdee632e0daceccb6f71b215392eba89230d626573f2fb4e9c0786c9a6610 bootDGSIv4="enode://${enodeDGSI}@[200.108.146.100]:30301" bootnodes="${bootARIUv6},${bootARIUv4},${bootUNCv4},${bootDGSIv4}" -function accountlist -{ - local accts= - local filename - for filename in ${BFANODEDIR}/keystore/*--* - do - if [ -r "$filename" -a ${#filename} -ge 40 ] - then - acct=${filename:$(( ${#filename} - 40 ))} - accts="${accts},${acct}" - fi - done - # strip leading comma - accts=${accts#,} - if [ ${#accts} -ge 40 ] - then - echo "--password /dev/null --unlock ${accts}" - fi -} - # touch the "miner" file if you are authorized to mine # If you don't want to restart after touching the file, # you can use attach.sh and issue the command: # miner.start() -function getminer -{ - if [ -e "${BFANODEDIR}/miner" ] - then - echo "--mine" - fi -} - function getsyncmode { local syncmode=$( cat "${BFANODEDIR}/syncmode" 2>/dev/null || true ) @@ -96,17 +68,11 @@ function startmonitor echo "A monitor is already running." false ) || exit - if [ -t 1 ] - then - echo Running monitor every 60 seconds. - fi - while : - do + ( monitor.js & echo $! > $pidfile wait - sleep 60 - done & + ) 2>&1 | ${BFAHOME}/bin/log.sh ${BFANODEDIR}/log & ) 9>> $pidfile } @@ -134,12 +100,11 @@ function startgeth echo '***' echo # (re)configure parameters (you never know if they changed) - flexargs="$( accountlist) $( getminer ) $( getsyncmode ) --extradata $( extradata )" + flexargs="$( getsyncmode ) --extradata $( extradata )" set -x geth \ --datadir ${BFANODEDIR} \ --networkid ${BFANETWORKID} \ - --bootnodes "${bootnodes}" \ --rpc \ --rpcport $rpcport \ --rpcapi "eth,net,web3,admin,clique,miner,personal" \ @@ -148,7 +113,8 @@ function startgeth --gcmode archive \ --cache 512 \ --verbosity ${BFAVERBOSITY:-3} \ - ${flexargs} & + ${flexargs} \ + --bootnodes "${bootnodes}" & set +x echo $! > ${BFANODEDIR}/geth.pid wait diff --git a/bin/unlock.js b/bin/unlock.js new file mode 100755 index 0000000000000000000000000000000000000000..d7f2315141f2adc0b8b71f7e36a06e72a6ecfd6f --- /dev/null +++ b/bin/unlock.js @@ -0,0 +1,126 @@ +#!/usr/bin/node + +const Libbfa = require( process.env.BFAHOME + '/bin/libbfa.js'); +const bfa = new Libbfa(); +const Writable = require('stream').Writable; +var mutableStdout = new Writable( + { + write: function( chunk, encoding, callback ) + { + if ( ! this.muted ) + process.stdout.write(chunk, encoding); + callback(); + } + } ); +mutableStdout.muted = false; +const rl = require('readline').createInterface( + { + input: process.stdin, + output: mutableStdout, + terminal: true, + historySize: 0 + } ); +var web3 = bfa.newweb3(); + +// First time we try to unlock, we will use this empty passphrase. +// Do not edit. +var passphrase = ""; + +function atLeastOneFailedSoGetNewPass(x) +{ + if ( !process.stdin.isTTY ) + bye( 0, "Stdin is not a tty. Will not try with other passwords." ); + // first we print the question + mutableStdout.muted = false; + rl.question( + "Enter another password to try: ", + (answer) => { + process.stdout.write( "\n" ); + mutableStdout.muted = false; + passphrase = answer; + if ( answer == "" ) + bye( 0, "Bye." ); + unlockall(); + } + ); + // Asking the question is an async event, so + // we set the object to mute output while the + // user types his password. + mutableStdout.muted = true; +} + +function bye( exitcode, msg ) +{ + console.log( msg ); + rl.close(); + process.exit( exitcode ); +} + +function unlockall() +{ + var wallets = new Array(); + web3.eth.personal.bfalistWallets() + .then( + function pushone(x) + { + var failures = 0; + var promises = new Array(); + var i = x.length; + while ( i-- > 0 ) + if ( x[i].status == "Locked" ) + wallets.push( x[i] ); + i = wallets.length; + if ( i == 0 ) + bye( 0, "List of accounts to unlock is empty." ); + while ( i-- > 0 ) + { + var j = wallets[i].accounts.length; + while ( j-- > 0 ) + { + var addr = wallets[i].accounts[j].address; + //console.log( "Trying to unlock " + addr + "." ); + var promise = + web3.eth.personal.unlockAccount( addr, passphrase, 0 ) + .catch( + error => + { + failures++; + return error; + } + ); + promises.push( promise ); + } + } + var empty = ""; + if ( passphrase == "" ) + empty = " with an empty passphrase"; + console.log( + "Attempting to unlock " + + promises.length + + " account" + ( promises.length == 1 ? '' : 's' ) + + empty + "." + ); + Promise.all( promises ) + .then( + function(x) + { + if ( failures == 0 ) + bye( 0, "OK." ); + console.log( failures + " account" + (failures==1 ? " is" : "s are") + " still locked." ); + atLeastOneFailedSoGetNewPass(); + }, + function(x) + { + console.log( x ); + bye( 1, "I can't imagine this text will ever be shown." ); + } + ); + }, + function errWalletList(x) + { + bye( 1, x ); + } + ) +} + +unlockall();