diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..23fa2fbe0c848cf37d89cebe97e7bd7693a3a2a9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +FROM ubuntu:xenial +MAINTAINER Francisco Ruiz BFA + +ENV DEBIAN_FRONTEND noninteractive +ENV BFAHOME=/bfa +ENV BFANETWORKDIR="${BFAHOME}/network5445" +ENV BFANETWORKID=5445 +ENV PATH=${PATH}:${BFAHOME}/bin + +WORKDIR /bfa + +RUN apt-get update && \ + apt-get -y -qq upgrade && \ + apt-get -y -qq install software-properties-common jq ncurses-bin curl && \ + add-apt-repository ppa:ethereum/ethereum && \ + apt-get update && \ + apt-get -y -qq install geth solc && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +COPY . /bfa/ + +RUN chmod +x /bfa/bin/cron.sh /bfa/bin/start.sh + +CMD bash -C '/bfa/bin/start.sh';'bash' + + +EXPOSE 8545 +EXPOSE 30303 diff --git a/README.md b/README.md index 1689a48b75db21636dec97e8954a7dc25d7294c8..bdd2872010996f26f85910244b45ef93c36f47f2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,96 @@ # Blockchain Federal Argentina +## TEST NETWORK -## Website: https://www.bfa.ar/ -## Container Repository. +## Official URL: https://gitlab.bfa.ar/blockchain/nucleo -Initial commit. \ No newline at end of file +1. Install geth + - For Debian read doc/compiling-geth-on-debian.txt + - For Ubuntu read doc/installing-geth-on-ubuntu.txt +2. `sudo apt install jq ncurses-bin curl` +3. `git clone https://github.com/rlegene/bfa.git` +4. `source ${HOME}/bfa/bin/env` + - You can include this line in your .bash_profile if you want. + - It is perfectly safe to source it multiple times. +5. Install this crontab: `@reboot bfa/bin/cron.sh` + - If you are running a sealer you MUST do this. +6. run `start.sh`. This will start synchronizing and probably takes at least an hour. +7. Change your node's settings with `syncmode.sh` + - Do this before you have synced too much in the step before, as it might remove all your downloaded chain data and restart synchronizing the chain. +8. Wait for it to finish synchronizing. +9. Run `maymine.sh` to update your configuration (detects if you are allowed to seal/mine or not). +10. Get some Ether from someone. Once you have some, you can try: + - Create your contract (there is already one deployed, but you can "overwrite" it with your own) + - Type lines of text into `tsa-insert.sh` (end with ctrl-D) +11. Free things to do with the BFA: + - Verify that the checksum has been seen with `tsa-verify.sh "<yourtexthere>"` + - If the text can not be found, it is because your insert transaction still isn't in the blockchain. Wait a bit and try again. + - Try the basic `explorer.sh`. It follows "latest" by default, but you can specify a block number as argument, e.g. `explorer.sh 0` to see genesis (block 0). + - Try out `walker.pl` +12. Install node.js so you can do better scripts locally: + `sudo apt-key adv --keyserver keyserver.ubuntu.com --recv 68576280` + `sudo apt-add-repository "deb https://deb.nodesource.com/node_7.x $(lsb_release -sc) main"` + `sudo apt-get update` + `sudo apt-get install nodejs` + `npm init -y` + `npm install web3` + +There are other "interesting" programs in the bin/ and src/ directories. + +## start.sh +requires: **geth** + +Starts a node on the 5445 BFA test net. Creates a genesis.json if you don't have one already. +One is already included in this package, which will allow you to connect to the existing BFA testnet. + +## attach.sh +request: **geth** + +Connects you to your running local geth. + +## compile.and.deploy.contract +requires: **geth**, **solc**, **jq** + +Compiles and deploys a contract to the blockchain. A local "node1" must already be running and synchronized. + +Argument 1 is the filename of the contract to compile. + +Example: `compile.and.deploy.contract src/TimestampAuthority.sol` + +## tsa-insert.sh +requires: **geth** + +Inserts the checksum of a text into the TSA. + +## tsa-verify.sh +requires: **geth** + +Returns the first blocknumber where the SHA256 checksum of a text was seen. The timestamp can then be found in the block (with `explorer.sh` for instance). + +## explorer.sh +requires: **curl**, **jq**, **tput** _(ncurses-bin)_, _(curl)_ + +Simple script to look at blocks + +## src/TimeStampAuthority.sol + +The initial Timestamp service. + +## walker.pl +requires: **geth**, **perl**, __(libjson-perl)__ + +## rewind.sh + +If your local node seems stuck and still connected, then you can try +this tool, and it'll back up a few blocks on the chain and try to catch +up from there. It really shouldn't happen, but it was seen a few times +when there were few sealers, that some nodes could get stuck on a side +fork. + +## log.sh + +Takes stdin and rotates it over a limited number of log files. We pipe +the output from `geth` into `log.sh`, so we still can read the log. + +## sendether.sh + +If you wish to give someone Ether, this script might be useful. diff --git a/bin/MasterDistiller.js b/bin/MasterDistiller.js new file mode 100755 index 0000000000000000000000000000000000000000..1fcabecc2becf1f6e8215fe7f3ee419f9b29e263 --- /dev/null +++ b/bin/MasterDistiller.js @@ -0,0 +1,264 @@ +#!/usr/bin/node + +const Libbfa = require( process.env.BFAHOME + '/bin/libbfa.js'); +const rl = require('readline').createInterface( + { input: process.stdin, output: process.stdout } + ); +var web3; +var Distillery; +var bfa; +var notation = [ 6 ]; + +function init() +{ + notation.push( 10**notation[0] ); + switch ( notation[0] ) { + case 6: + notation.push( "Mwei" ); + break; + case 9: + notation.push( "Gwei" ); + break; + default: + notation = [ 6, 10**6, "Mwei" ]; + } + bfa = new Libbfa(); + web3 = bfa.newweb3(); + Distillery = bfa.contract( web3, 'Distillery' ); + web3.eth.getBalance( Distillery.contractaddress ).then( + function receivedOwnBalance(val) { + Distillery.contractbalance = val; + getlist(); + }, + function err(x){ bfa.fatal("Can't do that: "+x);process.exit(1); } + ); +} + +function palletSort(a,b) +{ + if ( b == undefined ) + { + if ( a == undefined ) + return 0; + else + return -1; + } + if ( a == undefined ) + return 1; + var strA = a[0].toLowerCase(); + var strB = b[0].toLowerCase(); + if ( strA < strB ) + return -1; + if ( strA > strB ) + return 1; + return 0; +} + +function requestBalances( count ) +{ + var pallet = new Array; + var proms = new Array; + var i; + // Fetch addresses from the list in the contract. + for ( i=0; i<count; i++ ) + { + proms.push( + Distillery.methods.atPosition(i).call( {} ) + .then( + function(res) { pallet.push(res); }, + function(err) { bfa.fatal("Fetching position data failed: "+err) } + ) + ); + } + Promise.all( proms ).then( + function(x) { + // The Well has now been filled out + // so we will ask for the balances of the found accounts + var p2 = new Array(); + for ( i=0; i<count; i++ ) + { + var cb = setBal.bind(null,pallet,i); + p2.push( web3.eth.getBalance( pallet[i][0] ).then(cb) ); + } + Promise.all( p2 ).then( + function allbalances(a) { displayBalances(pallet) }, + function(err) { bfa.fatal("Getting balances failed: "+err) } + ); + }, + function(err) { bfa.fatal("Getting account list failed: "+err) } + ); +} + +function setBal( arr, idx, val ) +{ + arr[idx][2]=val; +} + +function editAccount( entry, pallet ) +{ + if ( entry == undefined ) + return; + var acct; + var value; + // it is an existing account address? + if ( bfa.isAddr(entry) ) + { + var i = 0; + var n = pallet.length; + while ( i < n ) + { + if (String(pallet[i][0]).toLowerCase() == String(entry).toLowerCase() ) + entry = i; + i++; + } + } + // it is a new account address? + if ( bfa.isAddr(entry) ) + { + acct = entry; + value = 0; + } + else + if ( bfa.isNumeric(entry) && entry < pallet.length ) + { + acct = pallet[entry][0]; + value = pallet[entry][1]; + } + else + if ( entry == "x" ) + { + // trigger distribution + Distillery.methods.distribute().send( {"from": bfa.account, "gas": 1000000 } ) + .then( + function distOK(x) { + console.log( + "Distribute returned succesfully in block# " + + x.blockNumber + + " using " + + x.gasUsed + + " gas." + ); + getlist(); + }, + function distFail(x) { + bfa.fatal( + "Distribute returned errors in block# " + + x.blockNumber + + " using " + + x.gasUsed + + " gas." + ); + console.log(x); + process.exit( 1 ); + } + ); + return; + } + else + bfa.fatal("I don't know what to do with \""+entry+"\"." ); + rl.question( + "Adjust the " + + notation[2] + + " fill value of " + + acct + + " (setting to 0 is the same as deleting)\n" + + "Amount?: ", + (answer) => { + if ( bfa.isNumeric(answer) ) + { + answer *= notation[1]; + console.log("Sending update to the SC..."); + Distillery.methods.setEtherAllowance(acct,answer) + .send( {"from": bfa.account } ) + .then( + function(a){ + console.log("Update accepted.") + getlist(); + }, + function(b){ + bfa.fatal( + "\nMaybe you are not authorized:\n" + +b + +"\n\n\nI think you should leave now.\n" + ); + } + ) + } + else + bfa.fatal( "I have no idea what to do with \""+answer+"\"." ); + rl.close; + } + ); +} + +function displayBalances( pallet ) +{ + var n = pallet.length; + var i; + pallet.sort(palletSort); + console.log( + "The contract's account (" + + Distillery.contractaddress + + ") has " + + Math.floor(Distillery.contractbalance/notation[1]).toFixed(0) + + " " + + notation[2] + + ".\n" + ); + var longest = 1; + for ( i=0; i<n; i++ ) + { + var len = (pallet[i][1]/notation[1]).toFixed(notation[0]).length; + if ( len > longest ) + longest = len; + } + for ( i=0; i<n; i++ ) + { + var entry = pallet[i]; + if ( entry == undefined ) + console.log( i+": <undef>" ); + else + { + var numstr = (pallet[i][1]/notation[1]).toFixed(notation[0]); + while ( numstr.length < longest ) + numstr = " "+numstr; + console.log( + i + + ": " + + pallet[i][0] + + " fills to " + + numstr + + " " + + notation[2] + + " (has " + + Number(pallet[i][2]/notation[1]).toFixed(notation[0]) + + ")." + ); + } + } + console.log("\n[ Q=quit x=distribute ]"); + rl.question("Which account to edit (enter index number of full account number)?: ", + (answer) => { + if ( answer != undefined ) + { + if ( String(answer).toUpperCase() == 'Q' ) + process.exit( 0 ); + else + editAccount(answer, pallet); + } + rl.close; + } + ) +} + +function getlist() +{ + Distillery.methods.numberOfBeneficiaries() + .call() + .then( + requestBalances, + function beneficiaryFail(x){bfa.fatal(x)} + ); +} + +init(); diff --git a/bin/attach.sh b/bin/attach.sh new file mode 100755 index 0000000000000000000000000000000000000000..41ade79de4cf27b0e7f5481ea72faf7ee39020d1 --- /dev/null +++ b/bin/attach.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# 20180626 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 + +geth_attach "$@" diff --git a/bin/bfalog.sh b/bin/bfalog.sh new file mode 100755 index 0000000000000000000000000000000000000000..69701e18e3be634005fd28e595aaaed6d058161c --- /dev/null +++ b/bin/bfalog.sh @@ -0,0 +1,8 @@ +#!/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 + +bfaconfig node +exec tail -n 100 -F ${BFANODEDIR}/log diff --git a/bin/checkreceipt.sh b/bin/checkreceipt.sh new file mode 100755 index 0000000000000000000000000000000000000000..d58cdacf06b7be943da04622008631857f41ec35 --- /dev/null +++ b/bin/checkreceipt.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +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 + +geth_exec "eth.getTransactionReceipt(\"$1\");" < /dev/null diff --git a/bin/compile.and.deploy.contract b/bin/compile.and.deploy.contract new file mode 100755 index 0000000000000000000000000000000000000000..f7a36c0ff88ab9452775a3aa6278b2b18678fe59 --- /dev/null +++ b/bin/compile.and.deploy.contract @@ -0,0 +1,70 @@ +#!/bin/bash +# 20180618 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 + +function create +{ + workdir=$( mktemp -p . -d ) + cleanup "$workdir" + json=$( solc --optimize --combined-json abi,bin $filename ); test $? = 0 + prefix=$( echo "$json" | jq -M '.contracts | keys | .[0]' ); + abi=$( echo "$json" | jq -rM ".contracts.${prefix}.abi" ); test $? = 0 + bin=$( echo "$json" | jq -rM ".contracts.${prefix}.bin" ); test $? = 0 + # Save abi for future use + echo $abi > ${workdir}/abi + # We could save the bin, but we don't actually use it later, plus + # it gets stored in the blockchain + #echo $bin > ${workdir}/bin + js=$( mktemp ) + cleanup "$js" + cat > $js <<EOT +var mycontract = eth.contract($abi) +var transaction = mycontract.new( { from: eth.accounts[0], data: "0x${bin}", gas: 1000000 } ) +var rcpt +while ( !rcpt ) +{ + admin.sleepBlocks( 1 ) + rcpt = eth.getTransactionReceipt( transaction.transactionHash ) +} +var address = rcpt.contractAddress +var pubcontract = mycontract.at(address) +console.log( pubcontract.address ) +EOT + echo '*** Creating contract. This will take at least 16 seconds.' + outfile=$( mktemp ) + cleanup "$outfile" + geth_exec_file $js > $outfile + if [ ` wc -l < $outfile ` = 2 -a `tail -1 < $outfile` = "true" ] + then + addr=` head -1 < $outfile ` + mkdir -p ${BFANETWORKDIR}/contracts + mv ${workdir} ${BFANETWORKDIR}/contracts/${addr} + echo Your new contract can be found in ${BFANETWORKDIR}/contracts/${addr} + ln -snf ${addr} ${BFANETWORKDIR}/contracts/${contractname} + else + echo + echo ' *** INPUT ***' + echo + cat $js + echo + echo ' *** OUTPUT ***' + echo + cat $outfile + fi +} + +filename="$1" +if [ -z "$filename" -o ! -r "$filename" ] +then + echo "Specify a filename of a contract you wish to compile." + false +fi +contractname=${filename%%.sol} +contractname=${contractname##*/} +contractname=${contractname##contract.} +contractname=${contractname%.*} +bfaconfig max +prereq jq solc geth +create diff --git a/bin/cron.sh b/bin/cron.sh new file mode 100755 index 0000000000000000000000000000000000000000..e1f8ca4b2ab16b5f263554635cc3dbfd8cfebdac --- /dev/null +++ b/bin/cron.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +trap 'exit 1' ERR +# Go $HOME +cd +# Log in home directory +exec < /dev/null > bfa-cron-output.log 2>&1 +# Go to script bin, 'cause we expect to find $BFAHOME/bin/env there +cd `dirname $0` +source ./env +( + ./start.sh & + # Yes, we wait 60 seconds after starting the server. + # If you don't want to wait, kill the sleep.. the || true + # will capture the ERR trap. + while sleep 60 || true + do + ./monitor.sh + done & +) diff --git a/bin/env b/bin/env new file mode 100644 index 0000000000000000000000000000000000000000..2139b8492cfa7cc8b1fb1db458bc2c8b657b5cc1 --- /dev/null +++ b/bin/env @@ -0,0 +1,4 @@ +export BFAHOME=${HOME}/bfa +export BFANETWORKDIR="${BFAHOME}/network5445" +export BFANETWORKID=5445 +PATH=${PATH}:${BFAHOME}/bin diff --git a/bin/explorer.sh b/bin/explorer.sh new file mode 100755 index 0000000000000000000000000000000000000000..10b69ea870ada6706cd75b3d35bdbef8569bb9e8 --- /dev/null +++ b/bin/explorer.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# 20180619 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 + +bfaconfig max +prereq tput curl +cd "${BFANETWORKDIR}" +width=$( tput cols ) +height=$( tput lines ) + +function showblock +{ + local hexblock + if [ "$1" = "latest" ] + then + hexblock="latest" + else + hexblock=$( printf '0x%x' $(( $1 )) ) + fi + local json=$( geth_rpc eth_getBlockByNumber \"${hexblock}\" true ) + if [ "${_onscreen}" != "$( echo $json | jq .hash )" ] + then + hexblock=$( echo $json | jq -r .number ) + printf '\e[H\e[JBlock %d (0x%x)\n' $(( $hexblock )) $(( $hexblock )) + echo $json | + jq -C . | + fold --width=$width | + head -$(( $height - 2 )) + _onscreen=$( echo $json | jq .hash ) + printf '\e[mj=up k=down l=latest q=quit ' + fi +} + +function latest +{ + local json=$( geth_rpc eth_blockNumber ) + local num=$( echo "$json" | jq -r . ) + # This arithmetic expansion in bash converts a hex number prefixed with 0x to a decimal number + echo $(( $num )) +} + +function tm +{ + if [ "$block" = "latest" ] + then + timeout="-t 1" + else + timeout= + fi +} + + +block=$1 +maxblock=$( latest ) +test -z "$block" && + block=latest +showblock $block +lastblock= +tm +while : +do + read -r -s -n 1 ${timeout} || true + maxblock=$( latest ) + case "${REPLY^^}" in + Q) + echo + exit 0 + ;; + K) + if [ "$block" = "latest" -a $maxblock -gt 0 ] + then + block=$(( $maxblock - 1 )) + else + if [ $block -gt 0 ] + then + block=$(( $block - 1 )) + fi + fi + ;; + J) + if [ "$block" != "latest" ] + then + block=$(( $block + 1 )) + fi + ;; + L) + block="latest" + ;; +# *) +# continue +# ;; + esac + lastblock=$block + showblock $block + tm +done diff --git a/bin/getbalance.sh b/bin/getbalance.sh new file mode 100755 index 0000000000000000000000000000000000000000..823cdf0dc7ef5a3584b44a6d25d2f78e78a8c653 --- /dev/null +++ b/bin/getbalance.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +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 + +function usage +{ + fatal "Usage: $0 <addr> [...<addr>]" +} + +prereq curl +test $# -ge 1 || usage + +( +while [ -n "$1" ] +do + echo "'$1'+' '+web3.fromWei(eth.getBalance('$1'), 'Ether');" + shift +done +) | geth_attach diff --git a/bin/libbfa.js b/bin/libbfa.js new file mode 100644 index 0000000000000000000000000000000000000000..4af1f4172993f9c9531d5fb07ae92c9591ee299f --- /dev/null +++ b/bin/libbfa.js @@ -0,0 +1,161 @@ +// 20180724 Robert Martin-Legene <robert@nic.ar> + +module.exports = class Libbfa +{ + constructor() { + this.fs = require('fs'); + this.Web3 = require('web3'); + this._account(); + } + + fatal( txt ) + { + console.log( txt ); + process.exit( 1 ); + } + + _home() + { + if ( this.home != undefined ) + return; + if ( process.env.BFAHOME == undefined ) + fatal( "$BFAHOME not set. Did you source bfa/bin/env ?" ); + this.home = process.env.BFAHOME; + } + + _getnetworkid() + { + if ( this.networkid != undefined ) + return; + this._home(); + if ( process.env.BFANETWORKID == undefined ) + { + var netw = new Array(); + this.fs.readdirSync( this.home ).forEach( function(file){ + if ( file.startWith('network') ) + netw.push( 0 + file.substring(7) ); + }); + if ( netw.length == 0 ) + fatal( "Can't determine your network ID." ); + netw.sort(); + process.env.BFANETWORKID= + this.networkid = netw[0]; + } + else + this.networkid = process.env.BFANETWORKID; + } + + _networkdir() + { + if ( this.networkdir != undefined ) + return; + this._getnetworkid(); + if ( process.env.BFANETWORKDIR == undefined ) + process.env.BFANETWORKDIR = + this.networkdir = process.env.BFAHOME + '/network' + process.env.BFANETWORKID; + else + this.networkdir = process.env.BFANETWORKDIR; + } + + _nodedir() + { + if ( this.nodedir != undefined ) + return; + this._networkdir(); + if ( this.networkdir == undefined ) + return; + if ( process.env.BFANODEDIR == undefined ) + { + var dirs = new Array(); + //var fs = this.fs; + var nwdir = this.networkdir; + this.fs.readdirSync( this.networkdir ).forEach( + function findnodedirs(f1) { + var name1 = [nwdir,f1].join('/'); + var fs = require('fs'); + if ( fs.statSync( name1 ).isDirectory() ) + { + fs.readdirSync( name1 ).forEach( + function lookforkeystores(f2) { + var name2 = [nwdir,f1,f2].join('/'); + if (f2 == "keystore" && fs.statSync( name2 ).isDirectory() ) + { + dirs.push( name1 ); + } + } + ) + } + } + ); + if ( dirs.length == 0 ) + return; + dirs.sort(); + process.env.BFANODEDIR = + this.nodedir = dirs[0]; + } + else + this.nodedir = process.env.BFANODEDIR; + this.netport = Number.parseInt( this.fs.readFileSync( this.nodedir + '/netport' ) ); + this.rpcport = Number.parseInt( this.fs.readFileSync( this.nodedir + '/rpcport' ) ); + } + + _account() + { + this._nodedir(); + if ( this.acct != undefined ) + return; + if ( process.env.BFANODEDIR == undefined ) + return; + var files = new Array(); + this.fs.readdirSync( process.env.BFANODEDIR + '/keystore' ).forEach( function(filename) { + if ( filename.includes('--') ) + files.push( filename ); + }); + // found none? + if ( files.length == 0 ) + return; + files.sort(); + this.account = '0x' + files[0].replace( /^.*--/, '' ); + } + + contract(w3, name) + { + this._networkdir(); + var contractdir = [ this.networkdir, 'contracts', name ].join('/'); + var contractaddress = this.fs.realpathSync( contractdir ).replace(/^.*\//, ''); + if ( contractaddress == undefined ) + return; + var abi = JSON.parse( + this.fs.readFileSync( contractdir + '/abi' ).toString() + ); + if ( abi == undefined ) + return; + var c = new w3.eth.Contract( abi, contractaddress ); + c.abi = abi; + c.contractaddress = contractaddress; + return c; + } + + newweb3() + { + this._nodedir(); + var provider = 'http://127.0.0.1:' + this.rpcport; + var w3 = new this.Web3( provider ); + w3.eth.extend({ + //property: 'bfaclique', + methods: [{ + name: 'getSigners', + call: 'clique_getSigners', + params: 0 + }] + }); + return w3; + } + isNumeric(n) { + return !isNaN(parseFloat(n)) && isFinite(n); + } + + isAddr(n) { + return n.length == 42 && n.substring(0,2) == "0x"; + } +} diff --git a/bin/libbfa.sh b/bin/libbfa.sh new file mode 100644 index 0000000000000000000000000000000000000000..f949c4943e649ec41a63e825c5a2503bb790a920 --- /dev/null +++ b/bin/libbfa.sh @@ -0,0 +1,343 @@ +# This should only be sourced, not executed directly. +# 20180626 Robert Martin-Legene <robert@nic.ar> + +trap "echo Argh! ; exit 1" ERR +set -e -o errtrace + +function fatal() +{ + echo "$@" >&2 + exit 1 +} + +trap "fatal Argh!" ERR +test -n "$BASH_VERSION" || + fatal "This file must be source(d) from bash." +test "$( caller 2>/dev/null | awk '{print $1}' )" != "0" || + fatal "This file must be source(d), not executed." + +function stderr +{ + echo "$@" >&2 +} + +function cleanup +{ + if [ $# -gt 0 ] + then + trap cleanup EXIT + cleanup_files="${cleanup_files} $@" + return + fi + rm -rf $cleanup_files +} + +function geth_attach +{ + bfaconfig node +# local cat=cat +# if echo $- | grep -q x +# then +# cat="tee /dev/tty |" +# fi +# $cat | + geth --cache 0 "$@" attach ipc:${BFANODEDIR}/geth.ipc +} + +function geth_exec_file +{ + test -r "$1" + if echo $- | grep -q x && [ -t 1 ] + then + sed "s/^/input: /" "$1" + fi + geth_attach --exec "loadScript(\"$1\")" </dev/null +} + +function geth_exec +{ + test -n "$1" + geth_attach --exec "$1" </dev/null +} + +function geth_rpc +{ + bfaconfig node + local cmd=$1 + test -n "$cmd" + local params= + shift + if [ $# -gt 0 ] + then + params=',"params":[' + while [ $# -gt 0 ] + do + params="${params}${1}," + shift + done + # Eat the last command and add a ] + params=${params/%,/]} + fi + local json=$( + curl \ + -H 'Content-type: application/json' \ + -X POST \ + --data "{\"jsonrpc\":\"2.0\",\"method\":\"${cmd}\"${params},\"id\":1}" \ + http://127.0.0.1:$rpcport \ + 2>/dev/null + ) + test -n "$json" + if [ "$( echo "$json" | jq .error )" != "null" ] + then + echo "$json" | jq -r .error.message >&2 + false + fi + echo "$json" | jq .result +} + +function getnetworkid +{ + test -n "${BFANETWORKID}" && + return + local dir="${BFANETWORKDIR}" + for dir in $( ls -1d "${BFAHOME}"/* | sort --version-sort ) + do + if [ -d "${dir}" ] + then + local base=$( basename "${dir}" ) + base=$( echo "${base}" | grep -E '^network[0-9]+$' ) || true + if [ -n "${base:7}" ] + then + BFANETWORKID=${base:7} + return + fi + fi + done +} + +function networkdir +{ + test -n "${BFAHOME}" -a -d "${BFAHOME}" || + fatal "\$BFAHOME in your environment must point to a directory." + # If no BFANETWORKDIR variable has been set, we will try to guess + # the network directory based on the directory we are in. + if [ -z "${BFANETWORKDIR}" ] + then + BFANETWORKDIR=$( + ls -1d "${BFAHOME}"/* | + grep -qE '/network[0-9]+$' | + sort --version-sort | + head -1 + ) + fi + test -n "${BFANETWORKDIR}" -a ! -d "${BFANETWORKDIR}" && + fatal "\$BFANETWORKDIR (\"${BFANETWORKDIR}\") not found." + if [ -z "${BFANETWORKDIR}" -o ! -d "${BFANETWORKDIR}" ] + then + local num=0 + while [ $num -lt 9876 ] + do + num=$(( $RANDOM * $RANDOM )) + done + stderr "I can not find your BFANETWORKDIR." + stderr "Consider running: mkdir ${BFAHOME}/network${num} or set BFANETWORKDIR in ${BFAHOME}/bin/env" + exit 1 + fi + getnetworkid + gen_genesis +} + +function nodedir +{ + networkdir + # set defaults + if [ -z "$BFANODEDIR" ] + then + # If there is no nodedir found, use the first node we + # find in the networkdir + BFANODEDIR=$( + ls -1d "${BFANETWORKDIR}"/*/keystore 2>/dev/null | + sort --version-sort | + head -1 + ) + if [ -z "${BFANODEDIR}" ] + then + BFANODEDIR="${BFANETWORKDIR}/node1" + echo "No node directories found. Initialising a new node." + geth --datadir ${BFANODEDIR} init ${BFAHOME}/src/genesis.json + else + # Get rid of the "keystore" label + BFANODEDIR=$( dirname $BFANODEDIR ) + fi + fi + test -n "${BFANODEDIR}" || + fatal "Unable to guess \$BFANODEDIR . Consider setting it explicitly in ${BFAHOME}/bin/env" + test -d "${BFANODEDIR}" || + fatal "$BFANODEDIR is not a directory." + test -d "${BFANODEDIR}/geth/chaindata" || + fatal "Node is not initialised. Consider running: geth --datadir $BFANODEDIR init ${BFAHOME}/src/genesis.json" + # Support migrating from "former" setups + if [ -r "${BFANODEDIR}/port" -a ! -r "${BFANODEDIR}/netport" ] + then + mv "${BFANODEDIR}/port" "${BFANODEDIR}/netport" + fi + # + test -r "${BFANODEDIR}/netport" || + echo $(( $RANDOM / 2 + 12345 )) > ${BFANODEDIR}/netport + netport=$( cat ${BFANODEDIR}/netport ) + test $? = 0 + test -r "${BFANODEDIR}/rpcport" || + echo $(( $RANDOM / 2 + 12345 )) > ${BFANODEDIR}/rpcport + rpcport=$( cat ${BFANODEDIR}/rpcport ) +} + +function extradata +{ + account + # something uniqueish + ## find default interface + local def_if=$( ( ip -4 route show ; ip -6 route show ) | expand | sed -ne '/^default /{s/ */ /g;s/^.* dev //;s/ .*//;p;q}' ) + local mymac=$( ip link show ${def_if} | sed -ne '/link\|ether/{s/^.*link.ether //;s/ .*//;s/://g;p;q}' ) + # + echo -n "${account:0:19}.${mymac:0:12}" +} + +function account +{ + nodedir + if [ -z "$account" ] + then + test -d "${BFANODEDIR}/keystore" + account=$( + ls -1dl "${BFANODEDIR}"/keystore/*--* 2>/dev/null | + head -1 | + sed 's/.*--//' + ) + fi + if [ -z "$account" ] + then + echo "No accounts found. Creating a new one (they are free)." + geth --datadir ${BFANODEDIR} --password /dev/null account new + if [ -n "$loop_protection" ] + then + fatal "Loop detected." + fi + loop_protection=1 + account + fi + unset loop_protection +} + +function bfaconfig +{ + case "$1" in + network) + networkdir + ;; + node) + nodedir + ;; + account|max) + account + ;; + *) + fatal "Unknown bfaconfig request" + ;; + esac +} + +function prereq +{ + err=0 + while [ -n "$1" ] + do + if ! which $1 > /dev/null + then + echo "Need $1" + err=1 + fi + shift + done + test $err -eq 0 +} + +function gen_genesis +{ + local genesis="${BFAHOME}/src/genesis.json" + test -e "${genesis}" && + return + bfaconfig account + local zero4="0000" + local zero8="${zero4}${zero4}" + local zero16="${zero8}${zero8}" + local zero20="${zero16}${zero4}" + local zero32="${zero16}${zero16}" + local zero40="${zero20}${zero20}" + local zero60="${zero40}${zero20}" + local zero64="${zero32}${zero32}" + local zero128="${zero64}${zero64}" + local zero130="${zero128}00" + local nodes=$account + local timestamp=$( printf '0x%08x' $( date +%s ) ) + local vanity_Blockchain="426c6f636b636861696e" + local vanity_Federal="4665646572616c" + local vanity_Argentina="417267656e74696e61" + local vanity_NIC="4e4943" + local vanity="${vanity_Blockchain}20${vanity_Federal}20${vanity_Argentina}20${vanity_NIC}" + ## Make sure vanity is exactly 64 characters + vanity="${vanity}${zero64}" + vanity="${vanity:0:64}" + echo -n "$vanity" | grep -Evq '[^0-9a-fA-F]' + cat <<-EOCONF > ${genesis} + { + "config": { + "chainId": ${BFANETWORKID}, + "homesteadBlock": 0, "eip150Block": 0, "eip155Block": 0, "eip158Block": 0, "byzantiumBlock": 4, + "eip150Hash": "0x${zero64}", + "clique": { "period": 15, "epoch": 30000 } + }, + "nonce": "0x0000000000000000", + "timestamp": "${timestamp}", + "extraData": "0x${vanity}${account}${zero130}", + "gasUsed": "0x0", "gasLimit": "0xffeeddcc", "difficulty": "0x1", + "number": "0x0", + "mixHash": "0x${zero64}", + "coinbase": "0x${zero40}", + "parentHash": "0x${zero64}", + "alloc": { "${account}": { "balance": "0x200${zero60}" } } + } + EOCONF +} + +function contract +{ + bfaconfig network + local contract="${BFANETWORKDIR}/contracts/${1}" + local realdir=$( realpath "${contract}" ) + test -r "${realdir}" + local address=$( basename "${realdir}" ) + test -n "${address}" + abi=$( cat ${contract}/abi ) + test -n "${abi}" + echo "eth.contract(${abi}).at(\"${address}\")" +} + +function contractSendTx +{ + local name=$1 + local func=$2 + shift 2 + echo "var contract = $( contract "${name}" );" + local args= + for x in $* + do + args="${args}, ${x}" + done + args="${args:1}," + if [ "$args" = "," ] + then + args= + fi + echo "contract.${func}.sendTransaction(${args} {from: eth.accounts[0], gas: 1000000} )" +} + diff --git a/bin/log.sh b/bin/log.sh new file mode 100755 index 0000000000000000000000000000000000000000..43e3068cb612fd3ac6e79ed0fe0e0030f0ac76b8 --- /dev/null +++ b/bin/log.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +trap "echo Argh; exit 1" ERR + +true ${MAXSIZE:=$((10*1024*1024))} + +log=$1 +test -n "$log" || + log=log + +exec >> "$log" +while read +do + echo "$REPLY" + size=$( stat -c '%s' "${log}" ) + if [ $size -ge $MAXSIZE ] + then + for gen in 8 7 6 5 4 3 2 1 + do + test -e "${log}.${gen}" && + mv -f "${log}.${gen}" "${log}.$(( ${gen} + 1 ))" + done + mv -f "${log}" "${log}.1" + exec >> "$log" + fi +done diff --git a/bin/maymine.sh b/bin/maymine.sh new file mode 100755 index 0000000000000000000000000000000000000000..594455c6220e8b2eb7551334f33ab929fbbce830 --- /dev/null +++ b/bin/maymine.sh @@ -0,0 +1,15 @@ +#!/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 + +bfaconfig node +res=$( geth_exec_file "${BFAHOME}/src/maymine.js" ) + +if [ "$res" = "true" ] +then + touch ${BFANODEDIR}/miner +else + rm -f ${BFANODEDIR}/miner +fi diff --git a/bin/monitor.sh b/bin/monitor.sh new file mode 100755 index 0000000000000000000000000000000000000000..ab3f83f8221acd9c26d359ebb9690c80a3004d33 --- /dev/null +++ b/bin/monitor.sh @@ -0,0 +1,90 @@ +#!/bin/bash + +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 + +function json_query +{ + cmd=$1 + params=$2 + if [ -n "$params" ] + then + params=",\"parameters\":[$2]" + fi + json=$( + curl -s -H 'Content-type: application/json' -X POST --data "{\"jsonrpc\":\"2.0\",\"method\":\"${cmd}\"${params},\"id\":1}" http://127.0.0.1:$rpcport + ) +} + +function json_err +{ + echo "$json" | jq -r '.result' +} + +function json_arraylength +{ + jq -r length +} + +function json_result +{ + echo "$json" | jq -r '.result' +} + +bfaconfig node +json_query admin_peers +numpeers=$( json_result | json_arraylength ) +statusfile=$( mktemp ) +cleanup "${statusfile}" +chmod 644 "${statusfile}" +echo -n "time: " > ${statusfile} +date >> ${statusfile} +echo "total-peer-count: ${numpeers}" >> ${statusfile} +peercount=0 +for idx in $( seq 0 $(( $numpeers - 1 )) ) +do + eth=$( echo "$json" | jq -r .result[$idx].protocols.eth ) + if [ "$eth" = "handshake" ] + then + continue + fi + #echo -n "$idx: "; echo "$eth" | jq -c + remoteid=$( + echo "$json" | + jq -r .result[$idx].id + ) + remoteaddress=$( + echo "$json" | + jq -r .result[$idx].network.remoteAddress + ) + remoteip=$( + echo "$remoteaddress" | + sed 's/:[0-9][0-9]*$//' + ) + # IPv6 has the address surrounded by [ and ] - we strip that away + if [ "${remoteip:0:1}" = "[" -a "${remoteip:$((${#remoteip}-1))}" = "]" ] + then + remoteip="${remoteip:1:$((${#remoteip}-2))}" + fi + remoteport=$( + echo "$remoteaddress" | + sed 's/^.*://' + ) + echo "peer: enode://${remoteid}@[${remoteip}]:${remoteport}" >> $statusfile + mkdir -p "${BFANETWORKDIR}/lastseen/${remoteid}" + if [ -r "${BFANETWORKDIR}/lastseen/${remoteid}/${remoteip}" ] + then + storedport=$( cat "${BFANETWORKDIR}/lastseen/${remoteid}/${remoteip}" ) + test "${remoteport}" = "${storedport}" && + unset remoteport + fi + if [ -n "${remoteport}" ] + then + echo "${remoteport}" > "${BFANETWORKDIR}/lastseen/${remoteid}/${remoteip}" + else + touch "${BFANETWORKDIR}/lastseen/${remoteid}/${remoteip}" + fi + peercount=$(( $peercount + 1 )) +done +echo "valid-peer-count: ${peercount}" >> $statusfile +mv -f $statusfile ${BFANETWORKDIR}/status diff --git a/bin/numpending.sh b/bin/numpending.sh new file mode 100755 index 0000000000000000000000000000000000000000..cd77368f44364e85c31ad97b613c7a9abcf0616f --- /dev/null +++ b/bin/numpending.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +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 + +echo txpool.status.pending | geth_attach | grep -Ev '^> $' | tail -1 +exit + + +function json_query +{ + cmd=$1 + params=$2 + if [ -n "$params" ] + then + params=",\"parameters\":[$2]" + fi + json=$( + curl -s -H 'Content-type: application/json' -X POST --data "{\"jsonrpc\":\"2.0\",\"method\":\"${cmd}\"${params},\"id\":1}" http://127.0.0.1:$rpcport + ) +} + +bfaconfig node +json_query web3j_txpool +echo $json | jq +exit + diff --git a/bin/rewind.sh b/bin/rewind.sh new file mode 100755 index 0000000000000000000000000000000000000000..947eb3c15d0e0af89c9e28b2906dd51d55ba6a9b --- /dev/null +++ b/bin/rewind.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +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 + +geth_exec_file ${BFAHOME}/src/rewind.js diff --git a/bin/sealeradd.sh b/bin/sealeradd.sh new file mode 100755 index 0000000000000000000000000000000000000000..938c75c1edd1405cfe124ed52c62d4bd37239379 --- /dev/null +++ b/bin/sealeradd.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# Robert Martin-Legene <robert@nic.ar> + +# Proposes to promote a new sealer or to demote an existing sealer. +# Also sends this vote to the new Sealers contract, so we have a place +# a contract can look up which addresses are sealers. + +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 + +operation="false" +if [ $( basename $0 ) == "sealeradd.sh" ] +then + operation="true" +fi +victim="$1" +( + echo "clique.propose(\"0x${victim#0x}\", ${operation});" + contractSendTx Sealers admin_promote \"0x${victim#0x}\" ${operation} +) | geth_attach diff --git a/bin/sealerrem.sh b/bin/sealerrem.sh new file mode 120000 index 0000000000000000000000000000000000000000..9f238ec6fc1160bcb11d5db94c4a25a38c68c150 --- /dev/null +++ b/bin/sealerrem.sh @@ -0,0 +1 @@ +sealeradd.sh \ No newline at end of file diff --git a/bin/sendether.sh b/bin/sendether.sh new file mode 100755 index 0000000000000000000000000000000000000000..eb147aeef8add216cc7bb9b5a7cf8acce333eeee --- /dev/null +++ b/bin/sendether.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +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 + +function usage +{ + fatal "Usage: $0 <dest-addr> <amount-of-ether>" +} + +test $# -eq 2 || usage + +rcpt=$1 +eth=$2 + +cat<<EOJS | geth_attach +eth.sendTransaction({from:eth.accounts[0], to: "${rcpt}", value: web3.toWei(${eth}, "ether")}) +EOJS diff --git a/bin/start.sh b/bin/start.sh new file mode 100755 index 0000000000000000000000000000000000000000..be39a51ee53e94b046def0d93a9cea23f062c2e0 --- /dev/null +++ b/bin/start.sh @@ -0,0 +1,89 @@ +#!/bin/bash + +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 + +bootnodeid=6c81b38551fec2f1142b58ed65137cc1b7dfdc7b35bc556ef26604c41e30fcdeb12212b3c19913584f71dc7bf87d76cd67fa523e96996c0f148390fb76fdc5f7 +bootnodev6=2800:40:1:6::135 +bootnodev4=200.68.65.135 +bootnodeport=5445 +bootnodes="enode://${bootnodeid}@[$bootnodev6]:${bootnodeport},enode://${bootnodeid}@[$bootnodev4]:${bootnodeport}" + +bfaconfig max + +function accountlist +{ + local accts= + for file in ${BFANODEDIR}/keystore/*--* + do + if [ -r "$file" ] + then + acct=$( echo $file | sed 's/^.*--//' ) + accts="${accts},${acct}" + fi + done + # strip first comma + echo "--password /dev/null --unlock ${accts:1}" +} + +# 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 ) + syncmode=${syncmode:-fast} + echo "--syncmode ${syncmode}" +} + + +# Start the miner. +( + flock --nonblock --exclusive 9 || exit 1 + if [ -t 1 ] + then + echo Logging everything to ${BFANODEDIR}/log + echo Consider running: tail -n 1000 -F ${BFANODEDIR}/log + fi + while : + do + echo + echo '***' + echo + # (re)configure parameters (you never know if they changed) + flexargs="$( accountlist) $( getminer ) $( getsyncmode ) --extradata $( extradata )" + set -x + geth \ + --datadir ${BFANODEDIR} \ + --networkid $BFANETWORKID \ + --bootnodes "${bootnodes}" \ + --rpc \ + --rpcport $rpcport \ + --rpcapi "eth,net,web3,admin,clique,miner,personal" \ + --port $netport \ + --nousb \ + --txpool.nolocals \ + --txpool.accountslots 128 \ + --txpool.globalslots 32768 \ + --txpool.accountqueue 512 \ + --txpool.globalqueue 8192 \ + --gcmode archive \ + --cache 512 \ + --verbosity 3 \ + ${flexargs} & + set +x + echo $! > ${BFANODEDIR}/geth.pid + wait + sleep 60 + done 2>&1 | ${BFAHOME}/bin/log.sh ${BFANODEDIR}/log & +) 9>> ${BFANODEDIR}/geth.pid diff --git a/bin/syncmode.sh b/bin/syncmode.sh new file mode 100755 index 0000000000000000000000000000000000000000..009d260899f968f030e86c503a46ec7bc4c8d9bf --- /dev/null +++ b/bin/syncmode.sh @@ -0,0 +1,82 @@ +#!/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 + +defaultmode="fast" + +echo "Available synchronization modes:" +echo " full : verify all blocks and all transactions since genesis (most secure)" +echo " fast : verify all blocks but not all transactions (faster than full, but less certain)" +echo " light: Makes this node into a light node which downloads almost" +echo " nothing, but relies on fast and full nodes in the network" +echo " to answer it's requests. This is the fastest and uses least" +echo " local resources, but outsources all trust to another node." +echo "Default mode is fast, because for many, it is a healthy compromise" +echo "between speed and paranoia. You can change the setting, according to" +echo "your needs." + +function modefilter +{ + case "$mode" in + "full"|"fast"|"light") + ;; + *) + echo "Unsupported mode." + mode="" + return + ;; + esac + true +} + +bfaconfig node +mode=$( cat ${BFANODEDIR}/syncmode 2>/dev/null || true ) +mode=${mode:-${defaultmode}} +orgmode=$mode +modefilter +echo "Your current mode is set to ${mode}" +killed=0 +mode= + +echo +while [ -z "${mode}" ] +do + read -p "Which mode do you wish? : " mode + modefilter +done +echo $mode > ${BFANODEDIR}/syncmode +if [ "$orgmode" = "fast" -a "$mode" = "full" ] +then + echo "You increased your paranoia level. The proper thing to do now," + echo "would be to delete your version of what you synchronized with" + echo "fast mode, and revalidate everything in the entire blockchain." + echo "This probably takes quite a long time and also requires downloading" + echo "all blocks from the entire blockchain again." + REPLY= + while [ "$REPLY" != "y" -a "$REPLY" != "n" ] + do + read -p "Do you wish to delete all downloaded blocks and resynchronize? [yn]: " + REPLY=${REPLY,,} + done + if [ "$REPLY" = "y" ] + then + if [ -r "${BFANODEDIR}/geth.pid" ] + then + local pid=$( cat ${BFANODEDIR}/geth.pid ) + kill -0 $pid 2>/dev/null && + echo "Killing running geth." && + killed=1 + while ! kill $pid 2>/dev/null + do + sleep 1 + done + fi + rm -fr ${BFANODEDIR}/geth/chainstate ${BFANODEDIR}/geth/lightchainstate + geth --cache 0 --datadir ${BFANODEDIR} init ${BFAHOME}/src/genesis.json + test $killed -eq 1 && + echo && + echo "The startup.sh should restart your geth shortly." + fi +fi diff --git a/bin/transactors.pl b/bin/transactors.pl new file mode 100755 index 0000000000000000000000000000000000000000..d5018b0a1aa1db91e731d40400da6583f0ebc233 --- /dev/null +++ b/bin/transactors.pl @@ -0,0 +1,262 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; +use IO::File; +use Math::BigInt; +use Carp; +$Carp::Verbose = 1; + +die "\$BFAHOME not set. Did you source bfa/bin/env ?\n" + unless exists $ENV{BFAHOME}; +chdir "$ENV{BFAHOME}" or die $!; + +package tools; +my $rpcport; +my $ua = LWP::UserAgent->new; + +sub new +{ + my $class = shift; + return bless {@_}, ref $class || $class; +} + +sub wait +{ + my $i = ++$_[0]->{'wait'}; + printf "%s%c[D", substr('|/-\\', $i%4, 1), 27; + sleep 1; +} + +sub cat($) +{ + my ($filename) = @_; + my $fh = IO::File->new($filename) or return; + return join('', $fh->getlines); +} + +sub hex2string($) +{ + my ($msg)=@_; + my $txt = ''; + while ($msg ne '') + { + my $i=hex( substr($msg,0,2) ); + my $c='.'; + $c=chr($i) if $i >= 32 and $i <= 127; + $txt .= $c; + $msg=substr $msg, 2; + } + return $txt; +} + +sub rpcreq +{ + my ( $opname, @params ) = @_; + if ( not defined $rpcport ) + { + $rpcport = tools::cat "$ENV{BFAHOME}/network5445/node1/rpcport" or die; + } + my $req = HTTP::Request->new( POST => "http://127.0.0.1:$rpcport" ); + $req->content_type('application/json'); + my $extra = scalar @params + ? sprintf(qq(,\"params\":[%s]), join(',', @params)) + : ''; + $req->content( qq({"jsonrpc":"2.0","method":"${opname}"${extra},"id":1})); + my $res = $ua->request($req); + die $res->status_line + unless $res->is_success; + return $res->content; +} + +package balance; +use JSON; + +sub new +{ + my ($class, $acct, $at) = @_; + my $self = bless {}, ref $class || $class; + $self->get( $acct, $at ) if defined $acct; + return $self; +} + +sub acct +{ + my ($self, $acct) = @_; + if ( defined $acct ) + { + $acct = '0x'.$acct if $acct !~ /^0x/; + $self->{'_acct'} = $acct; + } + return unless exists $self->{'_acct'}; + return $self->{'_acct'}; +} + +sub at +{ + my ($self, $at) = @_; + $self->{'_at'} = $at if defined $at; + return sprintf('0x%x', $self->{'_at'}) if exists $self->{'_at'}; + return 'latest'; +} + +sub get +{ + my ($self, $acct, $at) = @_; + $self->acct($acct) if defined $acct; + $self->at($at) if defined $at; + my @params = ( sprintf(qq("%s","%s"),$self->acct,$self->at) ); + my $content = tools::rpcreq( 'eth_getBalance', @params ); + my $json; + eval { $json = decode_json( $content ) }; + my $error = error->new( $content ); + if ( $error ) + { + my $msg = ''; + return 'NOTFOUND' if $error->message =~ /^missing trie node /; + die join(' * ', @params, $content); + return; + } + die if not exists $json->{'result'}; + die if not defined $json->{'result'}; + return Math::BigInt->from_hex( $json->{'result'} ); +} + + +package error; +use JSON; + +sub new +{ + my ($class, $json_in) = @_; + my $json; + eval { $json = decode_json( $json_in ) }; + return unless defined $json; + return unless exists $json->{'error'}; + my $self = bless { + '_code' => undef, + '_message' => undef, + }, ref $class || $class; + $self->code( $json->{'error'}->{'code'} ) if exists $json->{'error'}->{'code'}; + $self->message( $json->{'error'}->{'message'} ) if exists $json->{'error'}->{'message'}; + return $self; +} + +sub code +{ + my ( $self, $val ) = @_; + $self->{'_code'} = $val if scalar @_ > 1; + return $self->{'_code'}; +} + +sub message +{ + my ( $self, $val ) = @_; + $self->{'_message'} = $val if scalar @_ > 1; + return $self->{'_message'}; +} + +package block; +use LWP; +use JSON; + +sub new +{ + my ( $class, $json_raw ) = @_; + return unless defined $json_raw; + return if $json_raw eq ''; + my $self = bless {}, ref $class || $class; + $self->{'json_raw'} = $json_raw; + eval { $self->{'json'} = decode_json( $json_raw ) }; + return if $@; + $self->error( error->new($json_raw) ); + return $self; +} + +sub error +{ + return if not exists $_[0]->{'error'}; + return $_[0]->{'error'}; +} + + +sub json +{ + return unless exists $_[0]->{'json'}; + return $_[0]->{'json'}; +} + +sub result +{ + return unless exists $_[0]->{'json'}->{'result'}; + return $_[0]->{'json'}->{'result'}; +} + +sub number +{ + return if not exists $_[0]->result->{'number'}; + return hex( $_[0]->result->{'number'} ); +} + +sub timestamp +{ + return if not exists $_[0]->result->{'timestamp'}; + return hex( $_[0]->result->{'timestamp'} ); +} + +sub miner +{ + return if not exists $_[0]->result->{'miner'}; + return $_[0]->result->{'miner'}; +} + +sub get($;$) +{ + my ($number) = @_; + $number = sprintf('0x%x', $number) if $number ne 'earliest'; + my $cachefile = "cache/block.$number"; + my $content = tools::rpcreq( 'eth_getBlockByNumber', qq("$number"), "true"); + my $block = block->new( $content ); + return if not defined $block; + return if not exists $block->{'json'}; + die $block->error->message + if $block->error; + return if not $block->result; + return $block; +} + +sub tx { + my ($self) = @_; + return $self->result->{'transactions'}; +} + +package main; + +my $number = shift || 'earliest'; +my $_rcpts; + +sub rcpt +{ + return 0 if exists $_rcpts->{$_[0]}; + $_rcpts->{$_[0]} = 1; + return 1; +} + +while ( 1 ) +{ + my $block = block::get($number); + exit 0 if not defined $block; +use Data::Dumper; print Dumper( $block->result ); +die $block->miner; + rcpt( $block->miner ); + my $txref = $block->tx; + for my $tx (@$txref) + { + my $from = $tx->{'from'}; + my $to = $tx->{'to'}; + my $value = $tx->{'value'}; + next if $value eq '0x0'; + printf "%s\n", $to if rcpt( $to ); + } + $number = $block->number + 1; +} diff --git a/bin/tsa-insert.sh b/bin/tsa-insert.sh new file mode 100755 index 0000000000000000000000000000000000000000..1509e6d23d20aa04fae6bb23b086e9b15f6be112 --- /dev/null +++ b/bin/tsa-insert.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +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 + +contract="TimeStampAuthority" +contract=${BFANETWORKDIR}/contracts/${contract} +contract=$( realpath "${contract}" ) +test -r "${contract}" +basecontract=$( basename "${contract}" ) +test -n "${basecontract}" +abi=$( cat ${BFANETWORKDIR}/contracts/${basecontract}/abi ) +test -n "${abi}" +hashes= +while read -p : +do + sum=$( echo -n "$REPLY" | sha256sum | cut -c -64 ) + echo $sum + hashes="$hashes,web3.toDecimal(\"0x$sum\")" +done +hashes="${hashes:1}" +echo $hashes +js=$( mktemp ) +cleanup "$js" +cat > $js <<EOT +var mycontract = eth.contract(${abi}) +var thecontract = mycontract.at("${basecontract}") +console.log( thecontract.put.sendTransaction( [${hashes}], {from: eth.accounts[0], gas: 1000000} ) ) +EOT +out=$( mktemp ) +cleanup "$out" +geth_exec_file "${js}" > ${out} +if [ ` wc -l < ${out} ` = 2 -a ` tail -1 < ${out} ` = "true" ] +then + trans=` head -1 < ${out} ` + echo "Sent checksum(s) in transaction ${trans}." +else + ( + cat ${js} + echo + echo ' ***' + echo + cat ${out} + ) >&2 + exit 1 +fi diff --git a/bin/tsa-verify.sh b/bin/tsa-verify.sh new file mode 100755 index 0000000000000000000000000000000000000000..c2a055d51e67b879f3d9b5ab781d70683ba0304d --- /dev/null +++ b/bin/tsa-verify.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +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 + +contract="TimeStampAuthority" +contract=${BFANETWORKDIR}/contracts/${contract} +contract=$( realpath "${contract}" ) +test -r "${contract}" +basecontract=$( basename "${contract}" ) +test -n "${basecontract}" +abi=$( cat ${BFANETWORKDIR}/contracts/${basecontract}/abi ) +test -n "${abi}" +hashes= +sum=$( echo -n "$1" | sha256sum | cut -c -64 ) +echo $sum +hashes="web3.toDecimal(\"0x$sum\")" +echo $hashes +js=$( mktemp ) +cleanup "$js" +cat > $js <<EOT +var mycontract = eth.contract(${abi}) +var thecontract = mycontract.at("${basecontract}") +console.log( thecontract.get.call( [${hashes}], {from: eth.accounts[0], gas: 1000000} ) ) +EOT +out=$( mktemp ) +cleanup "$out" +geth_exec_file "${js}" > ${out} +if [ ` wc -l < ${out} ` = 2 -a ` tail -1 < ${out} ` = "true" ] +then + block=` head -1 < $out ` + if [ "${block}" -gt "0" ] + then + echo "Checksum first seen in block ${block}" + else + echo "The checksum has not been stored in the smart contract yet." + exit 1 + fi +else + ( + cat ${js} + echo + echo ' ***' + echo + cat ${out} + ) >&2 +fi diff --git a/bin/txflood.sh b/bin/txflood.sh new file mode 100755 index 0000000000000000000000000000000000000000..1d20bbde2ce66a82153c4194a86f6ca80db75979 --- /dev/null +++ b/bin/txflood.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +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 + +cd $BFAHOME + +while : +do + while num=` $BFAHOME/bin/numpending.sh `; [ $num -lt 2500 ] + do + contract=$( readlink ${BFANETWORKDIR}/contracts/TimestampDocument ) + abi=$( cat ${BFANETWORKDIR}/contracts/${contract}/abi ) + ( + echo "var mycontract = eth.contract(JSON.parse($abi));" + echo "var thecontract = mycontract.at(\"$contract\");" + n=0 + SECONDS=0 + while [ $n -lt 10000 -a $SECONDS -lt 600 ] + do + n=$(( $n + 1 )) + echo "thecontract.storeDocument.sendTransaction( \"dagenidag.${n}\", {from: eth.accounts[0], gas: 1000000});" + done + ) | + geth_attach + + done + echo `date`: $num pending. + sleep 2 +done + diff --git a/bin/walker.pl b/bin/walker.pl new file mode 100755 index 0000000000000000000000000000000000000000..038369fdf55d3385c9e287f385a137f09c4180ed --- /dev/null +++ b/bin/walker.pl @@ -0,0 +1,433 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; +use IO::File; +use Math::BigInt; +use Carp; +$Carp::Verbose = 1; + +die "\$BFAHOME not set. Did you source bfa/bin/env ?\n" + unless exists $ENV{BFAHOME}; +chdir "$ENV{BFAHOME}" or die $!; + +package tools; +my $rpcport; +my $ua = LWP::UserAgent->new; +our $CSI = "\x1b["; +our $clearEOS = "${CSI}J"; +our $up = "${CSI}A"; +our $red = "${CSI}41m"; +our $normal = "${CSI}m"; + +sub new +{ + my $class = shift; + return bless {@_}, ref $class || $class; +} + +sub wait +{ + my $i = ++$_[0]->{'wait'}; + printf "%s%c[D", substr('|/-\\', $i%4, 1), 27; + sleep 1; +} + +sub cat($) +{ + my ($filename) = @_; + my $fh = IO::File->new($filename) or return; + return join('', $fh->getlines); +} + +sub gmt +{ + my $ts = shift; + return unless defined $ts; + my @t = gmtime($ts); + $t[5]+=1900; + $t[4]++; + return sprintf('%04d%02d%02d-%02d%02d%02d', (@t)[5,4,3,2,1,0]); +} + +sub hex2string($) +{ + my ($msg)=@_; + my $txt = ''; + while ($msg ne '') + { + my $i=hex( substr($msg,0,2) ); + my $c='.'; + $c=chr($i) if $i >= 32 and $i <= 127; + $txt .= $c; + $msg=substr $msg, 2; + } + return $txt; +} + +sub rpcreq +{ + my ( $opname, @params ) = @_; + if ( not defined $rpcport ) + { + $rpcport = tools::cat "$ENV{BFAHOME}/network5445/node1/rpcport" or die; + } + my $req = HTTP::Request->new( POST => "http://127.0.0.1:$rpcport" ); + $req->content_type('application/json'); + my $extra = scalar @params + ? sprintf(qq(,\"params\":[%s]), join(',', @params)) + : ''; + $req->content( qq({"jsonrpc":"2.0","method":"${opname}"${extra},"id":1})); + my $res = $ua->request($req); + die $res->status_line + unless $res->is_success; + return $res->content; +} + +package balance; +use JSON; + +sub new +{ + my ($class, $acct, $at) = @_; + my $self = bless {}, ref $class || $class; + $self->get( $acct, $at ) if defined $acct; + return $self; +} + +sub acct +{ + my ($self, $acct) = @_; + if ( defined $acct ) + { + $acct = '0x'.$acct if $acct !~ /^0x/; + $self->{'_acct'} = $acct; + } + return unless exists $self->{'_acct'}; + return $self->{'_acct'}; +} + +sub at +{ + my ($self, $at) = @_; + $self->{'_at'} = $at if defined $at; + return sprintf('0x%x', $self->{'_at'}) if exists $self->{'_at'}; + return 'latest'; +} + +sub get +{ + my ($self, $acct, $at) = @_; + $self->acct($acct) if defined $acct; + $self->at($at) if defined $at; + my @params = ( sprintf(qq("%s","%s"),$self->acct,$self->at) ); + my $content = tools::rpcreq( 'eth_getBalance', @params ); + my $json; + eval { $json = decode_json( $content ) }; + my $error = error->new( $content ); + if ( $error ) + { + my $msg = ''; + return 'NOTFOUND' if $error->message =~ /^missing trie node /; + die join(' * ', @params, $content); + return; + } + die if not exists $json->{'result'}; + die if not defined $json->{'result'}; + return Math::BigInt->from_hex( $json->{'result'} ); +} + +package error; +use JSON; + +sub new +{ + my ($class, $json_in) = @_; + my $json; + eval { $json = decode_json( $json_in ) }; + return unless defined $json; + return unless exists $json->{'error'}; + my $self = bless { + '_code' => undef, + '_message' => undef, + }, ref $class || $class; + $self->code( $json->{'error'}->{'code'} ) if exists $json->{'error'}->{'code'}; + $self->message( $json->{'error'}->{'message'} ) if exists $json->{'error'}->{'message'}; + return $self; +} + +sub code +{ + my ( $self, $val ) = @_; + $self->{'_code'} = $val if scalar @_ > 1; + return $self->{'_code'}; +} + +sub message +{ + my ( $self, $val ) = @_; + $self->{'_message'} = $val if scalar @_ > 1; + return $self->{'_message'}; +} + +package block; +use LWP; +use JSON; + +sub new +{ + my ( $class, $json_raw ) = @_; + return unless defined $json_raw; + return if $json_raw eq ''; + my $self = bless {}, ref $class || $class; + $self->{'json_raw'} = $json_raw; + eval { $self->{'json'} = decode_json( $json_raw ) }; + return if $@; + $self->error( error->new($json_raw) ); + return $self; +} + +sub error +{ + return if not exists $_[0]->{'error'}; + return $_[0]->{'error'}; +} + + +sub json +{ + return unless exists $_[0]->{'json'}; + return $_[0]->{'json'}; +} + +sub result +{ + return unless exists $_[0]->{'json'}->{'result'}; + return $_[0]->{'json'}->{'result'}; +} + +sub number +{ + return if not exists $_[0]->result->{'number'}; + return hex( $_[0]->result->{'number'} ); +} + +sub td +{ + return if not exists $_[0]->result->{'totalDifficulty'}; + return hex( $_[0]->result->{'totalDifficulty'} ); +} + +sub timestamp +{ + return if not exists $_[0]->result->{'timestamp'}; + return hex( $_[0]->result->{'timestamp'} ); +} + +sub gasLimit +{ + return if not exists $_[0]->result->{'gasLimit'}; + return hex( $_[0]->result->{'gasLimit'} ); +} + +sub miner +{ + return if not exists $_[0]->result->{'miner'}; + return $_[0]->result->{'miner'}; +} + +sub nonce +{ + return if not exists $_[0]->result->{'nonce'}; + return lc $_[0]->result->{'nonce'}; +} + +sub hash +{ + return if not exists $_[0]->result->{'hash'}; + return lc $_[0]->result->{'hash'}; +} + +sub parentHash +{ + return if not exists $_[0]->result->{'parentHash'}; + return lc $_[0]->result->{'parentHash'}; +} + +sub extradata +{ + return if not exists $_[0]->result->{'extraData'}; + my $t = $_[0]->result->{'extraData'}; + return substr($t,2) if substr($t,0,2) eq '0x'; + return lc $t; +} + +sub sealers +{ + my $t = $_[0]->extradata; + return unless defined $t; + $t = substr $t,64; + $t = substr $t,0,-130; + my @a; + while ( length $t >= 40 ) + { + push @a, substr($t, 0, 40); + $t = substr $t, 40; + } + return @a; +} + +sub vanity +{ + my $t = $_[0]->extradata; + return unless defined $t; + return substr($t,0,64); +} + +sub clear +{ + my $t = shift; + return unless defined $t; + return substr($t,2) if substr($t,0,2) eq '0x'; + return $t; +} + +sub signature +{ + my ($self) = @_; + my $t = $self->extradata; + return unless defined $t; + return substr($t, -130); + my $res = $self->result; + die unless defined $res; +use Data::Dumper; +die Dumper($res). + clear($res->{'parentHash'}). + clear($res->{'sha3Uncles'}). + clear($res->{'miner'}). + clear($res->{'stateRoot'}). + clear($res->{'transactionsRoot'}). + clear($res->{'receiptsRoot'}). + clear($res->{'logsBloom'}). + clear($res->{'difficulty'}). + clear($res->{'number'}). + clear($res->{'gasLimit'}). + clear($res->{'gasUsed'}). + clear($res->{'timestamp'}). + substr( clear($res->{'extraData'}), 0, -130 ). ('0' x 130). + clear($res->{'mixHash'}). + clear($self->{'nonce'}); +} + +sub get($;$) +{ + my ($number) = @_; + $number = sprintf('0x%x', $number) if $number ne 'earliest'; + my $cachefile = "cache/block.$number"; + my $block; + if ( -r $cachefile ) + { + $block = block->new( tools::cat $cachefile ); + return $block + if defined $block and not $block->error; + # We delete the cache file if we couldn't use the data in it. + unlink $cachefile; + # and then we continue to fetch it + } + my $content = tools::rpcreq( 'eth_getBlockByNumber', qq("$number"), "false"); + $block = block->new( $content ); + return if not defined $block; + return if not exists $block->{'json'}; + die $block->error->message + if $block->error; + return if not $block->result; + my $fh = IO::File->new( $cachefile, 'w' ) + or die $!; + $fh->print( $block->{'json_raw'} ); + $fh->close; + return $block; +} + +sub delete_cache +{ + my ($self) = @_; + unlink sprintf('cache/block.0x%x', $self->number); +} + +sub print +{ + print scalar($_[0]->sprint),"\n"; +} + +my $nonce_xlate = { + '0x0000000000000000' => 'SEALER_REM', + '0xffffffffffffffff' => 'SEALER_ADD', +}; + +sub sprint +{ + my ( $self ) = @_; + my $txt = ''; + my $lines = 1; + my @sealers = $self->sealers; + if ( @sealers ) + { + $txt = sprintf "\r${tools::clearEOS}"; + $txt = ''; + for my $sealer ( @sealers ) + { + $txt.= sprintf + "Confirming signer at epoch: 0x%s with an ETH balance of %s\n", + $sealer, + balance->new->get($sealer, $self->number); + $lines++; + } + } + $txt .= sprintf + '%s block:%s gaslimit:%s td:%d Vanity: %s', + tools::gmt($self->timestamp), + $self->number, + $self->gasLimit, + $self->td, + tools::hex2string($self->vanity); + if ( $self->miner !~ /^0x0{40}$/o ) + { + # we have auth or drop + my $nonce = $self->nonce; + $nonce = $nonce_xlate->{$nonce} if exists $nonce_xlate->{$nonce}; + $txt .= sprintf " %s %s", $nonce, $self->miner; + } + return wantarray ? ($txt, $lines) : $txt; +} + +package main; + +$| = 1; +mkdir 'cache'; +my $number = shift || tools::cat 'walker.block.last' || 'earliest'; +my $tools = tools->new; +my @blks; +my $parent; + +while ( 1 ) +{ + my $block = block::get($number); + if ( not defined $block ) + { + $tools->wait(); + next; + } + $parent = block::get($block->number-1) if not defined $parent and $block->number > 1; + if ( defined $parent and $parent->hash ne $block->parentHash ) + { + printf "\r${tools::red}%s${tools::normal}\n", scalar($parent->sprint); + ($parent, $block, $number) = (undef, $parent, $number-1); + $block->delete_cache; + next; + } + shift @blks while scalar @blks > 32; + push @blks, $block; + $block->print; + $number = $block->number + 1; + $parent = $block; +} diff --git a/doc/compiling-geth-on-debian.md b/doc/compiling-geth-on-debian.md new file mode 100644 index 0000000000000000000000000000000000000000..f55b6d07cf076d0c7eee1734f7312b1cc67b740f --- /dev/null +++ b/doc/compiling-geth-on-debian.md @@ -0,0 +1,34 @@ +# Compiling geth on Debian + +## Prerequisites + +1. `mkdir ~/new ~/bin` +2. `cd ~/new` +3. `sudo apt install build-essential git libjson-perl` +4. `git clone https://github.com/ethereum/go-ethereum` + +## Compiling Go itself (takes less than 5 minutes) + +Go is only needed for compiling geth - afterwards we can delete it + +1. Download __go*.linux-amd64.tar.gz__ from [https://golang.org/dl/] e.g. [https://dl.google.com/go/go1.11.linux-amd64.tar.gz] +2. `tar -xzf go*.tar.gz` +3. `export PATH=${HOME}/new/go/bin:${PATH}:${HOME}/bin` +4. `cd go-ethereum` +5. `make geth` +6. `cp build/bin/geth ~/bin/` +7. `cd ..` +8. `rm -r ~/new/go` + +## Compililng Solc takes a while (like 20-50 times longer than compiling geth) + +If you wish to compile contracts too, compile Solidity as well. + +$ `git clone --recursive https://github.com/ethereum/solidity` +$ `cd solidity` +$ `git submodule update --init --recursive` +$ `./scripts/install_deps.sh` +$ `mkdir build` +$ `cd build` +$ `cmake .. && make` +$ `cp -p solc/solc ~/bin/` diff --git a/doc/installing-geth-on-ubuntu.md b/doc/installing-geth-on-ubuntu.md new file mode 100644 index 0000000000000000000000000000000000000000..050ae22d7f7221165fe516e291bcd2e0d6b0b157 --- /dev/null +++ b/doc/installing-geth-on-ubuntu.md @@ -0,0 +1,5 @@ +$ `su` +# `apt install software-properties-common libjson-perl` +# `add-apt-repository -y ppa:ethereum/ethereum` +# `apt update` +# `apt install ethereum` diff --git a/doc/whoiswho.txt b/doc/whoiswho.txt new file mode 100644 index 0000000000000000000000000000000000000000..4be4a0d426ef50fc395a05a4e027e87b47ce6650 --- /dev/null +++ b/doc/whoiswho.txt @@ -0,0 +1,25 @@ +NIC1 bfa@bc.duna.com.ar +Sealer 0x2fd693d1204907ae7d97b5d7d2e93ef877ef2c7d +enode://6c81b38551fec2f1142b58ed65137cc1b7dfdc7b35bc556ef26604c41e30fcdeb12212b3c19913584f71dc7bf87d76cd67fa523e96996c0f148390fb76fdc5f7@[200.68.65.135]:5445 +enode://6c81b38551fec2f1142b58ed65137cc1b7dfdc7b35bc556ef26604c41e30fcdeb12212b3c19913584f71dc7bf87d76cd67fa523e96996c0f148390fb76fdc5f7@[2800:40:1:6::135]:5445 + +CABASE1 apugawko@ +Sealer 0xf2d954738d49ff0fca43c7b3915e8499c983c5de +enode://d088f3e97e1b3d41eebe3d9596b61ed05c79a0a3d6496fe63f916a955e30b456dbd96b875ca160adea81c4341bb3049ad4c831f767d8bf948d96a48b51e97c71@[200.9.157.216]:23236 + +NIC2 robert@cloud.duna.com.ar +Sealer 0xbeebad827a9664d6be5be0f9393dd158826833c6 +enode://1a1abca46cf2f7dd1721c48df699c5b1af507c7953b8a52c353c718dbda06df43fc3e5bf43665a07dfb8be0f40f5f94c8b8224b2bd124639c89897d8774479be@[190.210.214.194]:21296 + +NIC3 Mariano Absatz +Sealer 0x95368af92200d72ae698466f44f9f4b34a7abb2f +enode://2076aec363e2429600b1629875841cf00a41c155b6fc8bfe5a525beb85cbff79712a6746b37969de74e4f6afe669e8fe512af50d0c47d40d8902647cb3f22c96@[50.116.48.41]:18844 + +DGSI +Sealer 0xa9cac6c2ef4909a05ef24a12ecadf9e541b5995f +enode://71d9972d30952db3e757954ce4e1c746a9f84b2d217e7378c1b8a1f48fb22b8960eb48e9be10a75df0968535f1916726c28869784d7ca110aa24bacd7a16c4ac@[200.108.145.9]:50310 + +RIU Luciano Minuchin +Sealer 0x737ce178ce5c97248a9a8794a78f30dce1d091ff +enode://015ab9ad905e84f3c943bb2ecded64ded00ac150ae06ff911f3fb9e76674b70fbc26ad6f9938194f7e4c607b4f8067f018d9c917250ad6e0a1e85366fea85fb8@[179.201.44.197]:28045 +enode://015ab9ad905e84f3c943bb2ecded64ded00ac150ae06ff911f3fb9e76674b70fbc26ad6f9938194f7e4c607b4f8067f018d9c917250ad6e0a1e85366fea85fb8@[2800:110:44:6120:b49a:3fff:fe74:5499]:28045 diff --git a/network5445/contracts/0x3935260bb04ee7e820fc03b7b271f1085f4365e3/abi b/network5445/contracts/0x3935260bb04ee7e820fc03b7b271f1085f4365e3/abi new file mode 100644 index 0000000000000000000000000000000000000000..05cd2b3cb6985df2d60c8a7df55b4e96f023040e --- /dev/null +++ b/network5445/contracts/0x3935260bb04ee7e820fc03b7b271f1085f4365e3/abi @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"hasharray","type":"uint256[]"}],"name":"put","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"hash","type":"uint256"}],"name":"get","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}] diff --git a/network5445/contracts/TimeStampAuthority b/network5445/contracts/TimeStampAuthority new file mode 120000 index 0000000000000000000000000000000000000000..5ce30c7def4d672255c9641c94843f012835c521 --- /dev/null +++ b/network5445/contracts/TimeStampAuthority @@ -0,0 +1 @@ +0x3935260bb04ee7e820fc03b7b271f1085f4365e3 \ No newline at end of file diff --git a/src/Distillery.sol b/src/Distillery.sol new file mode 100644 index 0000000000000000000000000000000000000000..f37872381de25cacc93dca148483c6a71138234f --- /dev/null +++ b/src/Distillery.sol @@ -0,0 +1,142 @@ +// vim:filetype=javascript +pragma solidity ^0.4.24; + +contract Distillery { + address owner; + struct Allowances { + address beneficiary; + uint topuplimit; + } + Allowances[] thelist; + // We use distpos to remember where we were stopped processing last time + // we were called. The idea is, that if we have too many accounts to take + // care of, and too little gasleft, then we stop before we run out of + // gas, since that would undo all the transactions we had already handled. + // Also, we don't want to favour anyone in particular in the list, such + // that some would have first priority in getting ether at every invocation + // So we continue checking the list from the same place where we left off + // at the previous invocation of distribute() + uint distpos; + + event distributeStartedBy( address activator ); + event setAllowance( address subject, uint amount ); + event xfrAllowance( address subject, uint amount ); + + constructor() public payable { + owner = msg.sender; + } + modifier onlyOwner { + require( msg.sender == owner ); + _; + } + // Using this function, you can find out how long thelist is. + function numberOfBeneficiaries() public view returns ( uint ) { + return thelist.length; + } + // Using this function, you get the address and topuplimit at a given position in thelist. + function atPosition( uint idx ) public view returns ( address, uint ) { + require( idx <= thelist.length, "There are not that many addresses in the list." ); + return (thelist[idx].beneficiary,thelist[idx].topuplimit); + } + // Returns a position +1 of where an address can be found in thelist. + // Or returns 0 if the address is not found in thelist. + // 0 : not found + // 1 : first position + function _beneficiaryPosition( address beneficiary ) internal view returns ( uint ) { + uint pos = thelist.length; + while ( pos-- > 0 ) + if ( beneficiary == thelist[pos].beneficiary ) + return pos+1; + return 0; + } + // This function returns the "allowance" that a given address is set to. + // Using this function, you don't have to cycle through atPosition() until + // you find the address you want to know about. + function getEtherAllowance( address beneficiary ) public view returns (uint256) { + uint pos = _beneficiaryPosition( beneficiary ); + if ( pos == 0 ) + return 0; + return thelist[pos-1].topuplimit; + } + // This admin (ownerOnly) function allows the creator of the contract to + // add/change/delete "allowances" per address. + function setEtherAllowance( address beneficiary, uint256 topuplimit ) public onlyOwner { + uint pos = _beneficiaryPosition( beneficiary ); + // Not found and trying to delete beneficiary? Just return immediately. + if ( pos == 0 && topuplimit == 0 ) + return; + emit setAllowance( beneficiary, topuplimit ); + // not found + if ( pos == 0 ) + { + if ( topuplimit > 0 ) + // Add the address to thelist because it was not already there + thelist.push( Allowances(beneficiary,topuplimit) ); + return; + } + // Now use a properly zero-indexed pos + pos--; + // + if ( topuplimit > 0 ) { + // Simple update the topuplimit of this address + thelist[pos].topuplimit = topuplimit; + return; + } + // The beneficiary is set to have 0 Ether, so we + // delete the beneficiary from the list + uint i = pos; + while ( i++ < thelist.length ) + thelist[i-1] = thelist[i]; + // Shorten the list + thelist.length--; + // If distpos was past the position that we removed, + // then move that back one. + if ( distpos >= pos ) + distpos--; + } + function selfDestruct() public onlyOwner { + selfdestruct( owner ); + } + function mayDistribute() public view returns ( bool ) + { + return msg.sender == owner; + } + function distribute() external { + require( mayDistribute(), "You are not authorized to activate the distribution functionality." ); + emit distributeStartedBy( msg.sender ); + // Is there anything to do at all + uint listlength = thelist.length; + if ( listlength == 0 ) + return; + // Has the list gotten shorter since we we were last time? + // This shouldn't happen, but it's better to be safe than to be sorry. + if ( distpos >= listlength ) + distpos = 0; + uint wheretostop = distpos; + while ( gasleft() > 54321 ) { + // Did we get to the end of the list, then start over + if ( ++distpos >= listlength ) + distpos = 0; + uint blockchainbalance = thelist[distpos].beneficiary.balance; + uint topuplimit = thelist[distpos].topuplimit; + uint diff = topuplimit - blockchainbalance; + // Don't top up anyone, if they still more than 90% of their allowance. + if ( blockchainbalance > topuplimit*9/10 ) + diff = 0; + if ( diff > 0 ) + { + // we use send() instead of transfer(), because + // transfer() can throw(), and we don't want + // to stop processing because of a single error. + // - + // Use || true to avoid warnings from the compiler. + emit xfrAllowance( thelist[distpos].beneficiary, diff ); + thelist[distpos].beneficiary.send( diff ) || true; + } + if ( wheretostop == distpos ) + return; + } + } + function () external payable { + } +} diff --git a/src/Sealers.sol b/src/Sealers.sol new file mode 100644 index 0000000000000000000000000000000000000000..0d5391880c70287c2a6e8447c4413c79e1afbc72 --- /dev/null +++ b/src/Sealers.sol @@ -0,0 +1,249 @@ +// vim:syntax:filetype=javascript:ai:sm + +pragma solidity ^0.4.24; + +// This contract is supposed to maintain the list of authorized sealers. +// For this to work, a sealer must deploy the contract and then all sealers +// must, say, every 5 minutes, run the related SealerSync.js + +contract Sealers { + + // This struct contains a list of votes and who has voted for each victim/beneficiary. + // Votes taking longer than 30K blocks, will have to restart. + struct Proposal { + address victim; // Whom are we voting about. + uint votestart; // In which block did this vote begin? + bool promotion; // true=promotion, false=demotion + address[] voters; // List of voters. + } + address[] public sealers; + Proposal[] public sealerproposals; + + event voteCast( address voter, address victim, bool promotionOrDemotion ); + event adminChange( address admin, bool promotionOrDemotion ); + + constructor() public + { + sealers.push( msg.sender ); + } + + // This function is used to know how many sealers are in the sealers list. + function sealerLength() public view returns (uint) + { + return sealers.length; + } + + // This function is to be used to get the address from a specific position in the sealers list. + // Remember first position is 0. + // Last position is sealers.length-1. + function sealerPosition( uint idx ) public view returns (address) + { + require( idx < sealers.length, "There are not that many sealers registered in the list." ); + return sealers[idx]; + } + + function isSealer( address subject ) public view returns (bool) + { + return ( _findAddressInList( sealers, subject ) > 0 ); + } + + function requireSealer( address subject ) public view + { + require( isSealer(subject), "Not sealer" ); + } + + // Returns an index to the position of the needle inside haystack. + // Beware that: + // 0 = not found. + // 1 = first position. + function _findAddressInList( address[] haystack, address needle ) private pure returns (uint) + { + uint i = haystack.length; + while ( i-- > 0 ) + if ( needle == haystack[i] ) + return i+1; + return 0; + } + + function _remove_proposal( uint idx ) private + { + // Move all items in the list (after idx) one step closer to the + // front of the list. + uint max = sealerproposals.length; + while ( ++idx < max ) + sealerproposals[idx-1] = sealerproposals[idx]; + // "pop" the end of the list, making the list shorter. + sealerproposals.length--; + } + + function _del_votes( address victim ) private + { + uint i_max = sealerproposals.length; + uint i = i_max; + // check all proposals + while ( i-- > 0 ) + { + // check all voters for every proposal + uint j_max = sealerproposals[i].voters.length; + uint j = j_max; + while ( j-- > 0 ) + if ( sealerproposals[i].voters[j] == victim ) + { + // Found the victim as voter, but since he is about + // to be deleted, we will remove him from the list. + uint k = j; + while ( ++k < j_max ) + sealerproposals[i].voters[k-1] = sealerproposals[i].voters[k]; + sealerproposals[i].voters.length--; + j_max--; + if ( sealerproposals[i].voters.length == 0 ) + { + _remove_proposal( i ); + i_max--; + } + } + } + } + + function _remove_sealer( address victim ) private + { + // Remove votes that the victim has already cast. + _del_votes( victim ); + // Move all items in the list (after match) one step closer to the + // front of the list. + uint max = sealers.length; + uint i = max; + while ( i-- > 0 ) + if ( sealers[i] == victim ) + { + // We could have recycled 'i' here, but for clarity, we don't. + uint j = i; + while ( ++j < max ) + sealers[j-1] = sealers[j]; + // "pop" the end of the list, making the list shorter. + sealers.length--; + return; + } + } + + // This function sees if any proposals have overstayed their welcome and thus + // needs to be removed. + function _trimProposals() private + { + uint i = sealerproposals.length; + while ( i-- > 0 ) + { + // If a proposal is more than 30K blocks old, then remove it from the list. + if ( sealerproposals[i].votestart + 30000 <= block.number ) + _remove_proposal( i ); + } + } + + // We run through the entire list of proposals, checking if they fulfill the + // requirements. Why the whole list? Because if a sealer is removed, whom has + // not yet voted for a proposal, that proposal may now have achieved majority. + function _promotedemote() private + { + uint prevlength = 0; + // Keep looping over the list until the number of proposals stops changing. + while ( prevlength != sealerproposals.length ) + { + uint i = sealerproposals.length; + prevlength = i; + uint majority = sealers.length / 2 + 1; + // Loop over all proposals + while ( i-- > 0 ) + { + // If we have enough votes to perform the actual promotion/demotion + if ( sealerproposals[i].voters.length >= majority ) + { + // Is it a promotion or a demotion? + if ( sealerproposals[i].promotion ) + // Add victim to sealer list + sealers.push( sealerproposals[i].victim ); + else + // Remove victim from sealer list + _remove_sealer( sealerproposals[i].victim ); + + // Send notification + emit adminChange( sealerproposals[i].victim, + sealerproposals[i].promotion ); + // Remove the proposal because the voting is complete. + _remove_proposal( i ); + } + } + } + } + // Returns an index to the position of the proposal inside matching the [victim,promotion] tuple + // Beware that: + // 0 = not found. + // 1 = first position. + function promotionIdx( address victim, bool promotion ) public view returns (uint) + { + uint i = sealerproposals.length; + while ( i-- > 0) + if ( sealerproposals[i].victim == victim && sealerproposals[i].promotion == promotion ) + return i+1; + return 0; + } + + // You can call this for free and know if your promote call + // will get accepted. Save the network, call this first. + function mayVote( address voter, address victim, bool promotion ) public view returns (bool) + { + // Is caller a sealer? + if ( !isSealer(voter) ) + return false; + + // Is already Sealer and want to promote him? + // Can't promote someone who is already a sealer. + if ( isSealer(victim) && promotion ) + return false; + + // Is not Sealer and want to demote him? + // Can't demote someone who is not a sealer. + if ( !isSealer(victim) && !promotion ) + return false; + + // See if the voter is already in the list of voters for this [victim,promotion] tuple + uint proppos = promotionIdx( victim, promotion ); + if ( proppos > 0 && _findAddressInList( sealerproposals[proppos-1].voters, voter ) > 0 ) + return false; + return true; + } + + // Calling this function will vote for adding an additional or + // removing an existing member of "the inner circle". + // As per usual, this requires n/2+1 votes. + // The boolean must be true if you want to add a sealer + // and false if you want to remove one. + function vote( address victim, bool promotion ) public + { + if ( ! mayVote(msg.sender, victim, promotion)) + revert("That seems redundant or is otherwise not allowed."); + _trimProposals(); + + // Send notification of the vote + emit voteCast( msg.sender, victim, promotion ); + + uint proppos = promotionIdx( victim, promotion ); + if ( proppos == 0 ) + { + // This is a new proposal, so we will add it, so we can vote on it. + address[] memory emptyAddressList; + proppos = sealerproposals.push( Proposal(victim, block.number, promotion, emptyAddressList ) ); + } + proppos--; + + // Add our vote + sealerproposals[proppos].voters.push( msg.sender ); + + // See if we're ready to promote/demote anyone, based on the proposals. + _promotedemote(); + + // If we have no more sealers, we have no reason to live. + if ( sealers.length == 0 ) + selfdestruct( msg.sender ); + } +} + diff --git a/src/TimeStampAuthority.sol b/src/TimeStampAuthority.sol new file mode 100644 index 0000000000000000000000000000000000000000..2282493d69c52e1bb7ed92a8548e32c2476b3d57 --- /dev/null +++ b/src/TimeStampAuthority.sol @@ -0,0 +1,26 @@ +// 20180718 Robert Martin-Legene <robert@nic.ar> +// Time stamp authority + +pragma solidity ^0.4.24; + +contract TimeStampAuthority { + // This mapping is almost an "associative array" + mapping (uint256 => uint) private hashstore; + + // Stores hashes (256 bit uint) of a document in the mapping + function put( uint256[] hasharray ) public { + uint256 i = hasharray.length; + while (i>0) { + i--; + uint256 h = hasharray[i]; + if (hashstore[h] == 0) { + hashstore[h] = block.number; + } + } + } + + // Returns the block number in which the hash was first seen + function get( uint256 hash ) public view returns (uint) { + return hashstore[hash]; + } +} diff --git a/src/genesis.json b/src/genesis.json new file mode 100644 index 0000000000000000000000000000000000000000..84812797e136ff53f9abde7ab6d68b2da207c9d9 --- /dev/null +++ b/src/genesis.json @@ -0,0 +1,16 @@ +{ + "config": { + "chainId": 5445, + "homesteadBlock": 0, "eip150Block": 0, "eip155Block": 0, "eip158Block": 0, "byzantiumBlock": 4, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "clique": { "period": 15, "epoch": 30000 } + }, + "nonce": "0x0000000000000000", + "timestamp": "0x5b293735", + "extraData": "0x426c6f636b636861696e204665646572616c20417267656e74696e61204e49432fd693d1204907ae7d97b5d7d2e93ef877ef2c7d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0xffeeddcc", "difficulty": "0x1", "number": "0x0", "gasUsed": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "alloc": { "2fd693d1204907ae7d97b5d7d2e93ef877ef2c7d": { "balance": "0x200000000000000000000000000000000000000000000000000000000000000" } } +} diff --git a/src/maymine.js b/src/maymine.js new file mode 100644 index 0000000000000000000000000000000000000000..d1857d7c96b307fe03a15b498ada5ace521773df --- /dev/null +++ b/src/maymine.js @@ -0,0 +1,10 @@ +// vim:syntax:filetype=javascript:ai:sm +// vim:expandtab:backspace=indent,eol,start:softtabstop=4 + +var signer +signer = (clique.getSigners().indexOf(eth.accounts[0]) > -1) +if (signer) + miner.start() +else + miner.stop() +signer diff --git a/src/rewind.js b/src/rewind.js new file mode 100755 index 0000000000000000000000000000000000000000..6c40497436354d079e86d233916d9b803876ee20 --- /dev/null +++ b/src/rewind.js @@ -0,0 +1,32 @@ +// vim:syntax:filetype=javascript:ai:sm +// vim:expandtab:backspace=indent,eol,start:softtabstop=4 + +// How many blocks to step back. +var backstep = 6 +// If we are syncing, there's no need to rewind (I think?) +if (!eth.syncing && eth.blockNumber > 10) +{ + var max = 0 + // Get the maximum difficulty of all valid connected peers + for (x in admin.peers) + { + var xd = admin.peers[x].protocols.eth.difficulty + if (admin.peers[x].protocols.eth!="handshake" && xd>max) + max=xd + } + if (eth.blockNumber.totalDifficulty+200<max) { + console.log( + "Max total difficulty is " + + max + + ", but mine is just " + + eth.blockNumber.totalDifficulty + + " (in block " + + eth.blockNumber + + "). Rolling " + + backstep+" blocks back, to block " + + web3.toHex(eth.blockNumber-backstep) + ) + // Rollback a bit and see if we weren't stuck just because we were stuck in a side fork. + debug.setHead(web3.toHex(eth.blockNumber-backstep)) + } +}