diff --git a/bin/walker.pl b/bin/walker.pl new file mode 100755 index 0000000000000000000000000000000000000000..95c6f8566e6253f470afef5053ef131aaa721c0a --- /dev/null +++ b/bin/walker.pl @@ -0,0 +1,377 @@ +#!/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 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 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 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; +} + +package main; + +my $nonce_xlate = { + '0x0000000000000000' => 'SEALER_REM', + '0xffffffffffffffff' => 'SEALER_ADD', +}; + +$| = 1; +mkdir 'cache'; +my $number = shift || tools::cat 'walker.block.last' || 'earliest'; +my $tools = tools->new; + +while ( 1 ) +{ + my $block = block::get($number); + if ( not defined $block ) + { + $tools->wait(); + next; + } + my $txt = sprintf + '%s block:%s gaslimit:%s Vanity: %s', + tools::gmt($block->timestamp), + $block->number, + $block->gasLimit, + tools::hex2string($block->vanity); + if ( $block->miner !~ /^0x0{40}$/ ) + { + # we have auth or drop + my $nonce = $block->nonce; + $nonce = $nonce_xlate->{$nonce} if exists $nonce_xlate->{$nonce}; + $txt .= sprintf " %s %s\n", $nonce, $block->miner; + } + else + { + my @sealers = $block->sealers; + if ( @sealers ) + { + printf "\r%s%c[J\n", $txt, 27; + $txt = ''; + for my $sealer ( @sealers ) + { + printf "Confirming signer at epoch: 0x%s with an ETH balance of %s\n", $sealer, balance->new->get($sealer, $block->number); + } + } + } + printf "\r%s%c[J", $txt, 27 if $txt ne ''; +#$block->signature; + $number = $block->number + 1; +}