diff --git a/bin/localstate.pl b/bin/localstate.pl new file mode 100755 index 0000000000000000000000000000000000000000..fc90f4eba9322e1a4753f157ff3d78eb476adeca --- /dev/null +++ b/bin/localstate.pl @@ -0,0 +1,276 @@ +#!/usr/bin/perl -w +# Robert Martin-Legene <robert@nic.ar> +# 20190423(c) NIC Argentina, GPLv2. + +# apt install libclass-accessor-perl libmath-bigint-perl + +use strict; +use warnings; +use Carp; +use Math::BigInt; +use JSON; +BEGIN { + die "\$BFAHOME not set. Did you source bfa/bin/env ?\n" + unless exists $ENV{BFAHOME}; +} +chdir $ENV{BFAHOME} or die $!; +use lib $ENV{'BFAHOME'}.'/bin'; +use libbfa; + +package error; +use JSON; +use Carp; + +sub new +{ + my ($class, $json_in) = splice(@_,0,2); + my $opt = pop @_ || {}; + my $self = bless { + '_code' => undef, + '_message' => undef, + }, ref $class || $class; + confess "No input JSON received, stopped" unless defined $json_in; + my $json; + if ( ref $json_in eq '' ) + { + eval { + $json = decode_json( $json_in ); + }; + confess $@ if $@; + } + if ( exists $json->{'error'} ) + { + $self->code( $json->{'error'}->{'code'} ) if exists $json->{'error'}->{'code'}; + $self->message( $json->{'error'}->{'message'} ) if exists $json->{'error'}->{'message'}; + confess sprintf( "%s: %s, stopped", $self->code || 'undef', $self->message || 'undef' ) + if $opt->{fatal}; + } + 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 Math::BigInt; +use base qw( Class::Accessor ); + +__PACKAGE__->mk_accessors(qw( + difficulty extraData gasLimit gasUsed hash logsBloom miner mixHash nonce + number parentHash receiptsRoot sha3Uncles size stateRoot timestamp + totalDifficulty transactions transactionsRoot uncles +)); + +sub new { + my ($class, $fields) = @_; + my $self = bless {}, ref $class || $class; + foreach my $k ( keys %$fields ) + { + $self->$k( ${$fields}{$k} ); + } + return $self; +} + +sub set +{ + my ($self, $key) = splice(@_, 0, 2); + + if (@_ == 1) + { + $_[0] = Math::BigInt->new( $_[0] ) + if $_[0] =~ /^(?:0x[\da-fA-F]+|\d+)$/; + $self->{$key} = $_[0]; + } + elsif (@_ > 1) + { + $self->{$key} = [@_]; + } + else + { + $self->_croak("Wrong number of arguments received"); + } +} + +package main; + +my $libbfa = libbfa->new(); +my $result; + +sub gmt +{ + my $ts = shift; + return unless defined $ts; + my @t = gmtime($ts); + $t[5] += 1900; + $t[4] ++; + return sprintf( + '%04d-%02d-%02dT%02d:%02d:%02dZ', + (@t)[5,4,3,2,1,0] + ); +} + +sub nicetimedelta +{ + my $s = shift; + my $t = ''; + my %intervals = ( w => 604800, d => 86400, h => 3600, m => 60, s => 1 ); + foreach my $letter (qw/ w d h m s /) + { + my $n = $intervals{$letter}; + if ( $s >= $n or $t ne '') + { + $t .= sprintf '%02d%s', $s / $n, $letter; + $s %= $n; + } + } + return $t eq '' ? '0s' : $t; +} + +sub rpc +{ + my ($libbfa, $procedure, @args) = @_; + my $content = $libbfa->rpcreq( $procedure, @args ); + confess unless $content; + my $json = decode_json( $content ); + confess "$content, stopped" unless defined $json; + error->new( $json, {fatal=>1} ); + return $json->{'result'}; +} + +### syncing ? +my $syncing = rpc( $libbfa, 'eth_syncing' ); +if ( $syncing ) +{ + my $current = Math::BigInt->new( $syncing->{'currentBlock'} ); + my $highest = Math::BigInt->new( $syncing->{'highestBlock'} ); + my $starting = Math::BigInt->new( $syncing->{'startingBlock'} ); + my $startgap = $highest->copy->bsub( $starting ); + my $synced = $current->copy->bsub( $starting ); + my $pct = 100.0 * $synced / $startgap; + printf "%d%% done syncing %d blocks.\n", int($pct), $startgap; +} +else +{ + print "We are currently not syncing (this is normal if you have all the blocks).\n"; +} + +### mining ? +$result = rpc( $libbfa, 'eth_mining' ); +if ( $result ) +{ + printf "We mine when we can.\n"; +} +else +{ + print "We do not mine.\n"; +} + +### latest block +$result = rpc( $libbfa, 'eth_getBlockByNumber', '"latest"', "true" ); +my $block = block->new( $result ); +my $timediff = time()-$block->timestamp; +printf + "Our latest block number is %d. It's timestamp says %s (%s old).\n", + $block->number, + gmt($block->timestamp), + nicetimedelta( $timediff ); +print "WHY IS IT SO OLD?\n" + if $timediff > 90; + +# List peers +$result = rpc( $libbfa, 'admin_peers' ); +# Can be undef if the admin module is not enabled in geth (opentx). +if ( defined $result ) +{ + my $i = 0; + foreach my $peer ( sort { $a->{'network'}{'remoteAddress'} cmp $b->{'network'}{'remoteAddress'} } @$result ) + { + if ( ref $peer->{'protocols'}->{'eth'} eq 'HASH' ) + { + print "[I]n/[O]ut [S]tatic [T]rusted\n" if $i == 0; + $i++; + my $ip = $peer->{'network'}->{'remoteAddress'}; + $ip =~ s/:\d+$//; #port + $ip =~ s/^\[(.*)\]$/$1/; #ipv6 + printf "[%s%s%s] %s\n", + $peer->{'network'}->{'inbound'} ? 'I' : 'O', + $peer->{'network'}->{'static'} ? 'S' : ' ', + $peer->{'network'}->{'trusted'} ? 'T' : ' ', + $ip; + } + } + printf "We are connected to %d peer%s.\n", $i, $i==1?'':'s'; +} + +# See recent signers - but skip if we are syncing, since that makes little sense. +my %signers; + $result = rpc( $libbfa, 'clique_getSnapshot', '"latest"' ); + # Can be undef if the clique module is not enabled in geth (opentx). + if ( defined $result ) + { + %signers = %{$result->{'signers'}}; + } + if ( defined $result and not defined $syncing ) + { + my %recents = %{$result->{'recents'}}; + my $i = -98765; + foreach my $s ( sort keys %signers ) + { + $signers{$s} = $i--; + } + foreach my $n ( keys %recents ) + { + my $actor = $recents{$n}; + $signers{$actor} = $n; + } + my $nplusone = int(scalar(keys %signers)/2)+1; + my $threshold = $block->number->copy->binc->bsub($nplusone); + foreach my $s ( sort { $signers{$a} <=> $signers{$b} } keys %signers ) + { + my $this = Math::BigInt->new( $signers{$s} ); + my $cmp = $threshold->bcmp( $this ); + next unless defined $cmp; + if ( $cmp < 0 ) + { + printf "Signer %s signed block %d recently.\n", $s, $this; + } + else + { + printf "Signer %s is allowed to sign next block.\n", $s; + } + } + } + +## List accounts +$result = rpc( $libbfa, 'eth_accounts' ); +if ( $result ) +{ + my $i = 0; + if ( scalar @$result ) + { + foreach my $account ( sort @$result ) + { + my $maymine = ''; + $maymine = ' sealer' + if exists $signers{$account}; + printf "Locally available account%s:\n", scalar @$result == 1 ? '' : 's' + if $i++ == 0; + printf "Account %d: %s%s\n", $i, $account, $maymine; + } + } + else + { + print "No accounts are locally available.\n"; + } +}