From fec5980e5c7cdd7837d3418ab83d81d972b70840 Mon Sep 17 00:00:00 2001
From: Francisco Ruiz <francisco@ruiz.com.ar>
Date: Thu, 4 Oct 2018 18:05:47 -0300
Subject: [PATCH] v000000

---
 Dockerfile                                    |  29 ++
 README.md                                     |  96 +++-
 bin/MasterDistiller.js                        | 264 +++++++++++
 bin/attach.sh                                 |   7 +
 bin/bfalog.sh                                 |   8 +
 bin/checkreceipt.sh                           |   6 +
 bin/compile.and.deploy.contract               |  70 +++
 bin/cron.sh                                   |  20 +
 bin/env                                       |   4 +
 bin/explorer.sh                               |  98 ++++
 bin/getbalance.sh                             |  20 +
 bin/libbfa.js                                 | 161 +++++++
 bin/libbfa.sh                                 | 343 ++++++++++++++
 bin/log.sh                                    |  26 ++
 bin/maymine.sh                                |  15 +
 bin/monitor.sh                                |  90 ++++
 bin/numpending.sh                             |  27 ++
 bin/rewind.sh                                 |   6 +
 bin/sealeradd.sh                              |  20 +
 bin/sealerrem.sh                              |   1 +
 bin/sendether.sh                              |  18 +
 bin/start.sh                                  |  89 ++++
 bin/syncmode.sh                               |  82 ++++
 bin/transactors.pl                            | 262 +++++++++++
 bin/tsa-insert.sh                             |  46 ++
 bin/tsa-verify.sh                             |  47 ++
 bin/txflood.sh                                |  31 ++
 bin/walker.pl                                 | 433 ++++++++++++++++++
 doc/compiling-geth-on-debian.md               |  34 ++
 doc/installing-geth-on-ubuntu.md              |   5 +
 doc/whoiswho.txt                              |  25 +
 .../abi                                       |   1 +
 network5445/contracts/TimeStampAuthority      |   1 +
 src/Distillery.sol                            | 142 ++++++
 src/Sealers.sol                               | 249 ++++++++++
 src/TimeStampAuthority.sol                    |  26 ++
 src/genesis.json                              |  16 +
 src/maymine.js                                |  10 +
 src/rewind.js                                 |  32 ++
 39 files changed, 2857 insertions(+), 3 deletions(-)
 create mode 100644 Dockerfile
 create mode 100755 bin/MasterDistiller.js
 create mode 100755 bin/attach.sh
 create mode 100755 bin/bfalog.sh
 create mode 100755 bin/checkreceipt.sh
 create mode 100755 bin/compile.and.deploy.contract
 create mode 100755 bin/cron.sh
 create mode 100644 bin/env
 create mode 100755 bin/explorer.sh
 create mode 100755 bin/getbalance.sh
 create mode 100644 bin/libbfa.js
 create mode 100644 bin/libbfa.sh
 create mode 100755 bin/log.sh
 create mode 100755 bin/maymine.sh
 create mode 100755 bin/monitor.sh
 create mode 100755 bin/numpending.sh
 create mode 100755 bin/rewind.sh
 create mode 100755 bin/sealeradd.sh
 create mode 120000 bin/sealerrem.sh
 create mode 100755 bin/sendether.sh
 create mode 100755 bin/start.sh
 create mode 100755 bin/syncmode.sh
 create mode 100755 bin/transactors.pl
 create mode 100755 bin/tsa-insert.sh
 create mode 100755 bin/tsa-verify.sh
 create mode 100755 bin/txflood.sh
 create mode 100755 bin/walker.pl
 create mode 100644 doc/compiling-geth-on-debian.md
 create mode 100644 doc/installing-geth-on-ubuntu.md
 create mode 100644 doc/whoiswho.txt
 create mode 100644 network5445/contracts/0x3935260bb04ee7e820fc03b7b271f1085f4365e3/abi
 create mode 120000 network5445/contracts/TimeStampAuthority
 create mode 100644 src/Distillery.sol
 create mode 100644 src/Sealers.sol
 create mode 100644 src/TimeStampAuthority.sol
 create mode 100644 src/genesis.json
 create mode 100644 src/maymine.js
 create mode 100755 src/rewind.js

diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..23fa2fb
--- /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 1689a48..bdd2872 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 0000000..1fcabec
--- /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 0000000..41ade79
--- /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 0000000..69701e1
--- /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 0000000..d58cdac
--- /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 0000000..f7a36c0
--- /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 0000000..e1f8ca4
--- /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 0000000..2139b84
--- /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 0000000..10b69ea
--- /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 0000000..823cdf0
--- /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 0000000..4af1f41
--- /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 0000000..f949c49
--- /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 0000000..43e3068
--- /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 0000000..594455c
--- /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 0000000..ab3f83f
--- /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 0000000..cd77368
--- /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 0000000..947eb3c
--- /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 0000000..938c75c
--- /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 0000000..9f238ec
--- /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 0000000..eb147ae
--- /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 0000000..be39a51
--- /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 0000000..009d260
--- /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 0000000..d5018b0
--- /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 0000000..1509e6d
--- /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 0000000..c2a055d
--- /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 0000000..1d20bbd
--- /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 0000000..038369f
--- /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 0000000..f55b6d0
--- /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 0000000..050ae22
--- /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 0000000..4be4a0d
--- /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 0000000..05cd2b3
--- /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 0000000..5ce30c7
--- /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 0000000..f378723
--- /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 0000000..0d53918
--- /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 0000000..2282493
--- /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 0000000..8481279
--- /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 0000000..d1857d7
--- /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 0000000..6c40497
--- /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))
+    }
+}
-- 
GitLab