From 12b111012b31e071d89bd88a5df58d092516bec5 Mon Sep 17 00:00:00 2001
From: Robert Martin-Legene <robert@martin-legene.dk>
Date: Mon, 25 Feb 2019 08:49:16 -0300
Subject: [PATCH] First step trying to watch sealers in colours

---
 bin/libbfa.pm      |  26 ++-
 bin/sealerwatch.pl | 451 +++++++++++++++++++++++++++++++++++++++++++++
 network/aliases    |  15 ++
 3 files changed, 482 insertions(+), 10 deletions(-)
 create mode 100755 bin/sealerwatch.pl
 create mode 100644 network/aliases

diff --git a/bin/libbfa.pm b/bin/libbfa.pm
index 56dc58a..f3fcfbc 100644
--- a/bin/libbfa.pm
+++ b/bin/libbfa.pm
@@ -10,7 +10,7 @@ $Carp::Verbose  =   1;
 
 sub     _cat
 {
-    my      $filename   =   shift;
+    my      ( $self, $filename )    =   @_;
     my      $fh         =   IO::File->new($filename);
     return if not defined $fh;
     local   $_          =   join( '', $fh->getlines );
@@ -26,8 +26,7 @@ sub     _filecontents_or_default
 {
     my  ($self, $filename, $default)   =   @_;
     local   $_  =   $self->_cat( $filename );
-    return $default if not defined $_;
-    return $_;
+    return  defined $_ ? $_ : $default;
 }
 
 sub     new
@@ -68,7 +67,8 @@ sub     new
     }
     #
     $self->{'netport'}  =   $self->_filecontents_or_default( $self->{'nodedir'}.'/netport', 30303 );
-    $self->{'rpcport'}  =   $self->_filecontents_or_default( $self->{'nodedir'}.'/netport',  8545 );
+    $self->{'rpcport'}  =   $self->_filecontents_or_default( $self->{'nodedir'}.'/rpcport',  8545 );
+    $self->{'rpchost'}  =   $self->_filecontents_or_default( $self->{'nodedir'}.'/rpchost', 'http://localhost' );
     $self->{'ua'}       =   LWP::UserAgent->new;
     return $self;
 }
@@ -92,14 +92,20 @@ sub     contract
 
 sub     rpcreq
 {
-    my  ( $self, $opname, @params ) = @_;
-    my  $req        =   HTTP::Request->new( POST => "http://127.0.0.1:".$self->{'rpcport'} );
-    $req->content_type('application/json');
-    my $extra      =   scalar @params
+    my  ( $self, $opname, @params )
+                                =   @_;
+    my      $ua                 =   $self->{'ua'}->clone;
+    $ua->ssl_opts( 'verify_hostname' => 0 );
+    my      $endpoint           =   sprintf '%s:%d', $self->{rpchost}, $self->{'rpcport'};
+    my      $extra              =   scalar @params
         ? sprintf(qq(,\"params\":[%s]), join(',', @params))
         : '';
-    $req->content( qq({"jsonrpc":"2.0","method":"${opname}"${extra},"id":1}));
-    my  $res        =   $self->{'ua'}->request($req);
+    #
+    my      $res                =   $ua->post(
+        $endpoint,
+        'Content-Type'          =>  'application/json',
+        'Content'               =>  qq({"jsonrpc":"2.0","method":"${opname}"${extra},"id":1}),
+    );
     die $res->status_line
         unless $res->is_success;
     return $res->content;
diff --git a/bin/sealerwatch.pl b/bin/sealerwatch.pl
new file mode 100755
index 0000000..9c01dd7
--- /dev/null
+++ b/bin/sealerwatch.pl
@@ -0,0 +1,451 @@
+#!/usr/bin/perl -w
+
+use     strict;
+use     warnings;
+use     Math::BigInt;
+use     Carp;
+$Carp::Verbose                  =   1;
+BEGIN {
+    die "\$BFAHOME not set. Did you source bfa/bin/env ?\n"
+        unless exists $ENV{BFAHOME};
+}
+use     lib $ENV{'BFAHOME'}.'/bin';
+use     libbfa;
+my      $libbfa;
+
+package ansi;
+
+our     $CSI                    =   "\x1b[";
+sub     CUP             { $CSI.(shift||1).';'.(shift||1).'H' }
+sub     EL              { $CSI.(shift||0).'K'   }
+sub     ED              { $CSI.(shift||0).'J'   }
+sub     normal          { $CSI.'m'              } 
+sub     black           { $CSI.'30m'            }
+sub     red             { $CSI.'41m'            }
+sub     green           { $CSI.'42m'            }
+sub     brightwhite     { $CSI.'97m'            }
+sub     bgyellow        { $CSI.'103m'           }
+
+package alias;
+use     IO::File;
+
+our     $initialised            =   0;
+our     %list;
+
+sub     translate
+{
+    my      $victim             =   lc shift;
+    if ( ! $initialised )
+    {
+        $initialised            =   1;
+        local   $_              =   undef;
+        my      $fh             =   IO::File->new($libbfa->{'networkdir'}.'/aliases');
+        if ( defined $fh )
+        {
+            while ( $_ = $fh->getline )
+            {
+                s|^\s+||;
+                s|\s+$||;
+                my      @e      =   split /\s+/, $_, 2;
+                if ( scalar @e == 2 )
+                {
+                    my  $addr   =   lc $e[0];
+                    $list{$addr}=   $e[1];
+                }
+            }
+            $fh->close;
+        }
+    }
+    return $list{$victim} if exists $list{$victim};
+    return;
+}
+
+package tools;
+our     $CSI                    =   "\x1b[";
+our     $clearEOS               =   "${CSI}J";
+our     $up                     =   "${CSI}A";
+our     $red                    =   "${CSI}41m";
+our     $normal                 =   "${CSI}m";
+
+sub     new
+{
+    my ($class, $libbfa)        =   @_;
+    my $self                    =   bless {}, ref $class || $class;
+    return $self;
+}
+
+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) );
+    $txt                        .=  ( $i >= 32 and $i <= 127 ) ? chr($i) : '.';
+    $msg                        =   substr $msg, 2;
+  }
+  return $txt;
+}
+
+sub     max(@)
+{
+    my      $num                =   0;
+    local   $_                  =   undef;
+    foreach $_ (@_)
+    {
+        $num                    =   $_
+            if $num < $_;
+    }
+    return $num;
+}
+
+package error;
+use     JSON;
+
+sub     new
+{
+    my      ($class, $json_in)  =   @_;
+    my      $self               =   bless {
+        '_code'                 =>  undef,
+        '_message'              =>  undef,
+    }, ref $class || $class;
+    my      $json;
+    eval {
+        $json                   =   decode_json( $json_in )
+    };
+    return unless defined $json;
+    return unless exists  $json->{'error'};
+    $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, $libbfa )     =   @_;
+    my  $self                   =   bless {}, ref $class || $class;
+    $self->{'libbfa'}           =   $libbfa if defined $libbfa;
+    return $self;
+}
+
+sub     parse
+{
+    my  ( $self, $json_raw )    =   @_;
+    return unless defined $json_raw;
+    return if $json_raw eq '';
+    $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     get
+{
+    my  ($self, $number)        =   @_;
+    my  $libbfa                 =   $self->{'libbfa'};
+    my  $hexed                  =   $number =~ /^\d+$/ ? sprintf("0x%x",$number) : $number;
+    my  $content                =   $libbfa->rpcreq( 'eth_getBlockByNumber', qq("$hexed"), "false");
+    my  $block                  =   block->new( $libbfa );
+    $block->parse( $content );
+    return if not exists $block->{'json'};
+    die $block->error->message if $block->error;
+    return if not $block->result;
+    return $block;
+}
+
+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\n",
+                $sealer;
+            $lines++;
+        }
+    }
+    $txt                        .=  sprintf
+        '%s block:%s %s',
+        tools::gmt($self->timestamp),
+        $self->number,
+        $self->sealer;
+    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;
+use     JSON;
+
+$| = 1;
+chdir "$ENV{BFAHOME}" or die $!;
+my      $number                 =   shift || 'latest';
+my      $tools                  =   tools->new;
+my      %cache;
+my      $lastblock;
+my      %signers;
+
+$libbfa                         =   libbfa->new();
+my      $block                  =   block->new( $libbfa )->get( $number );
+die if not defined $block;
+$number                         =   $block->number;
+print ansi::CUP().ansi::ED();
+
+sub     determine_colour
+{
+    my      $diff               =   shift;
+    return ansi::green() . ansi::brightwhite()
+        if $diff == 0;
+    return ansi::green()
+        if $diff < scalar ( keys %signers );
+    return ansi::bgyellow() . ansi::black
+        if $diff < 720; # one hour
+    return ansi::red();
+}
+
+sub colour_split
+{
+    my      ($name, $diff, $col)      =   @_;
+    return $name
+        if $diff == 0;
+    my  $len                    =   length $name;
+    return
+        substr( $name, 0, $len-$diff) .
+        (( $col eq '' ) ? ansi::green() : $col) .
+        substr( $name, $len-$diff ) .
+        ansi::normal()
+            if $diff <= $len;
+    # diff > len
+    return $col . $name . ansi::normal();
+}
+
+sub     presentation_top
+{
+    my  $block                  =   shift;
+    return if not defined $block;
+    #
+    my      $warning            =   '';
+    $warning                    =   '  ' . ansi::red() . " **NOT RECENT** " . ansi::normal()
+        if $block->timestamp + 1800 < time();
+    print
+        ansi::CUP(),
+        ansi::normal(),
+        tools::gmt($block->timestamp),
+        $warning,
+        ansi::EL(0),
+        ansi::CUP(scalar(keys %signers) + 2, 1);
+}
+
+while ( defined $block || sleep 1 )
+{
+    my      $parent             =   undef;
+    $block                      =   block->new( $libbfa )->get( $number );
+    if ( not defined $block )
+    {
+        presentation_top( $lastblock );
+        next;
+    }
+    presentation_top( $block );
+    $lastblock                  =   $block;
+    $cache{$number}{'block'}    =   $block;
+    $number                     =   $block->number;
+    if ( exists $cache{ $number - 1 }{'block'} )
+    {
+        $parent                 =   $cache{ $number - 1}{'block'};
+        # If we do have any information about previous blocks,
+        # see if the hash matches. If we were in a side branch
+        # we would eventually get wrong hashes, because we
+        # ask for blocknumbers (block height).
+        # This is a good way to determine if we're side tracked.
+        if ( $parent->hash ne $block->parentHash )
+        {
+            # If we are side tracked, we'll read backwards
+            # until we find a match (or nothing cached)
+            delete $cache{$number};
+            $number             --;
+            next;
+        }
+    }
+    my      $hexed              =   $number =~ /^\d+$/ ? sprintf("0x%x",$number) : $number;
+    my      $snapjson           =   $libbfa->rpcreq( 'clique_getSnapshot', qq("$hexed") );
+    die     unless defined $snapjson;
+    die     if $snapjson eq '';
+    my      $snapparsed;
+    $snapparsed                 =   decode_json( $snapjson );
+    my      $result             =   $snapparsed->{'result'};
+    my      $thissigner         =   $result->{'recents'}{$number};
+    #
+    # Make sure we only have current signers listed
+    my      %newsigners         =   ();
+    for my $this (sort keys %{ $result->{'signers'} } )
+    {
+        $newsigners{$this}      =   $signers{$this}
+            if exists $signers{$this};
+    }
+    %signers                    =   %newsigners;
+    $signers{$thissigner}       =   $number;
+    $cache{$number}{'signer'}   =   $thissigner;
+    #
+    # presentation
+    my      $num_max_width      =   tools::max( length $number, 3 );
+
+    print ansi::CUP(2,1);
+    foreach my $this ( sort keys %{ $result->{'signers'}} )
+    {
+        my      $lastnum        =   exists $signers{$this} ? $signers{$this} : -12345;
+        my      $diff           =   $number - $lastnum;
+        my      $col            =   determine_colour( $diff );
+        my      $id             =   colour_split( $this, $diff, $col );
+        my      $flags          =   $diff == 0 ? '*' : '';
+        my      $alias          =   alias::translate( $this );
+        my      $numtxt         =   (not defined $lastnum or $lastnum < 0) ? 'n/a' : $lastnum;
+        printf "%s%-1s%s%-${num_max_width}s%s %s%s\n",
+            $id, $flags, $col, $numtxt, ansi::normal(),
+            defined $alias ? $alias : '',
+            ansi::EL(0);
+    }
+    print ansi::ED(0);
+    #
+    $number                     =   $block->number + 1;
+}
diff --git a/network/aliases b/network/aliases
new file mode 100644
index 0000000..e1b0a18
--- /dev/null
+++ b/network/aliases
@@ -0,0 +1,15 @@
+0x91c055c6478bd0ad6d19bcb58f5e7ca7b04e67f1 Dirección General de Sistemas de Información
+0x46991ada2a2544468eb3673524641bf293f23ccc Universidad Nacional de Córdoba
+0x342e1d075d820ed3f9d9a05967ec4055ab23fa1e CABASE CABA
+0x02665f10cb7b93b4491ac9594d188ef2973c310a CABASE Mendoza
+0x19fe7b9b3a1bebde77c5374c8e13c623e3d1b5b2 Asociación Redes de Interconexión Universitaria
+0x2feb6a8876bd9e2116b47834b977506a08ea77bd Prefectura Naval Argentina
+0x9b3ac6719b02ec7bb4820ae178d31c0bbda3a4e0 everis
+0x609043eBDE4a06bD28a1DE238848E8f82cca9C23 Universidad Nacional de San Juan
+0xc0310a7b3b25f49b11b901a667208a3eda8d7ceb Servicios y Telecomunicaciones S.A.
+0x998c2651db6f76ca568c0071667d265bcc1b1e98 Agencia de Sistemas de Información, CABA
+0x39170a1ce03729d141dfaf8077c08b72c9cfdd0c IXP Bahia Blanca
+0x2388d2cdb2cd6e7722b4af39c3bb406dd31f560e Universidad Nacional de Rosario
+0x401d7a8432caa1025d5f093276cc6ec957b87c00 Oficina Nacional de Tecnologías de Información
+0x97a47d718eab9d660b10de08ef42bd7fd915b783 Universidad Nacional de La Plata
+0xa14152753515674ae47453bea5e155a20c4ebabc Universidad de Palermo
-- 
GitLab