diff --git a/bin/libbfa.pm b/bin/libbfa.pm index 56dc58a07dcf85379213e408b24e738b20f60a6d..f3fcfbc0ac45ab11e4813f7b9725bb398d0aabad 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 0000000000000000000000000000000000000000..9c01dd7305d69d983a36bff2c8a27ee50f69caff --- /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 0000000000000000000000000000000000000000..e1b0a18b4592071b24175e8b552bab0600d140c6 --- /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