From d1780003c010779bc3eee5ad9c17bf8b1ac02c9c Mon Sep 17 00:00:00 2001
From: Robert Martin-Legene <robert@martin-legene.dk>
Date: Thu, 16 Jul 2020 14:34:27 -0300
Subject: [PATCH] non-working import

---
 .gitignore                 |   1 +
 collector/Dockerfile       |   9 +
 collector/collector.pl     | 417 +++++++++++++++++++++++++++++++++++++
 collector/sql.pm           | 108 ++++++++++
 docker-compose.yml         |  33 +++
 postgres/10-postgres.sql   |  97 +++++++++
 postgres/90-add-db-user.sh |   8 +
 postgres/Dockerfile        |   2 +
 8 files changed, 675 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 collector/Dockerfile
 create mode 100755 collector/collector.pl
 create mode 100644 collector/sql.pm
 create mode 100644 docker-compose.yml
 create mode 100644 postgres/10-postgres.sql
 create mode 100755 postgres/90-add-db-user.sh
 create mode 100644 postgres/Dockerfile

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..178b7d0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.vstags
diff --git a/collector/Dockerfile b/collector/Dockerfile
new file mode 100644
index 0000000..2c114b4
--- /dev/null
+++ b/collector/Dockerfile
@@ -0,0 +1,9 @@
+FROM debian
+RUN useradd --create-home --shell /bin/bash bfa && mkdir /home/bfa/collector
+RUN apt-get update && apt-get upgrade -y
+RUN apt-get install -y libjson-perl libnet-dns-perl libdbd-pg-perl libwww-perl vim
+COPY collector.pl /home/bfa/collector
+RUN chown -R bfa /home/bfa
+USER bfa
+#CMD /home/bfa/collector/collector.sh
+CMD sleep 14d
diff --git a/collector/collector.pl b/collector/collector.pl
new file mode 100755
index 0000000..a14bbe6
--- /dev/null
+++ b/collector/collector.pl
@@ -0,0 +1,417 @@
+#!/usr/bin/perl -w
+# 20200108 Robert Martin-Legene <robert@martin-legene.dk> / <robert@nic.ar>
+# (c)2020 Secretaría Legal y Técnica de la Presidencia de la Nación
+# LGPLv2-only
+
+use     warnings;
+use     strict;
+use     Data::Dumper;
+use     Math::BigInt;
+use     LWP;
+use     JSON;
+use	    Net::DNS;
+use     Net::DNS::Resolver;
+use     DBI;
+use     "sql"
+
+# 47525974938;
+my      $netversion         =   Math::BigInt->new( "0xb10c4d39a" );
+# 200941592
+my      $chainid            =   Math::BigInt->new( "0xbfa2018"   );
+my      $idcounter          =   0;
+my      $ua;
+my      @endpoints;
+my      @blockqueue;
+my      %sealers;
+# Set maxruns to 1 if you want to just do a single run to keep
+# your database up-to-date.
+my      $maxruns            =   0;
+
+sub     info(@)
+{
+    # if 1 param only
+    unshift @_, '%s'
+        if $#_ == 0;
+    my      $format         =   shift;
+    $format                 =   "%s: " . $format;
+    printf  $format, astime(time), @_;
+}
+
+sub     astime
+{
+    my  @t                  =   localtime( $_[0] );
+    $t[5]                   +=  1900;
+    $t[4]                   +=  1;
+    return sprintf '%04d%02d%02d-%02d%02d%02d', @t[5,4,3,2,1,0];
+}
+
+sub     shorthash($)
+{
+    local   $_              =   $_[0];
+    s/^(0x.......).*(.......)$/$1..$2/;
+    return $_;
+}
+
+sub     rpcreq
+{
+    my  ( $endpoint,
+        $opname, @params )  =   @_;
+    # $ua->ssl_opts( 'verify_hostname' => 0 );
+    my      %args           =   (
+        jsonrpc             =>  '2.0',
+        method              =>  $opname,
+        id                  =>  $idcounter++,
+    );
+    my  @args               =   map {
+            my  $v          =   $args{$_};
+            $v              =   qq/"${v}"/ if $v !~ /^\d+$/;
+            sprintf qq/"%s":%s/, $_, $v;
+        } keys %args;
+    if ( scalar @params )
+    {
+        my  $p              =   '"params":[';
+        foreach my $param ( @params )
+        {
+            $param = '"' . $param . '"'
+                if  $param ne 'true'
+                and $param ne 'false'
+                and $param !~ /^\d+$/;
+            $p              .=  $param . ',';
+        }
+        chop $p;
+        $p                  .=  ']';
+        push @args, $p;
+    }
+    my  $args               =   '{' . join(',',@args) . '}';
+    my  $res                =   $ua->post(
+        $endpoint,
+        'Content-Type'      =>  'application/json',
+        'Content'           =>  $args,
+    );
+    die $res->status_line
+        unless $res->is_success;
+    my  $json               =   decode_json($res->content);
+    return $json->{'result'};
+}
+
+sub     endpoint_find
+{
+    my      $networknumber  =   $netversion->bstr;
+    my      $res            =   Net::DNS::Resolver->new();
+    my      $origin         =   'bfa.martin-legene.dk';
+    my      $fqdn           =   sprintf
+        '_rpc._tcp.public.%s.%s.',
+        $networknumber, $origin;
+    my	    $reply          =   $res->query( $fqdn, 'SRV' );
+    die "No SRV RR found for public endpoints. Stopped"
+        if not $reply;
+    my      @answer;
+    @answer                 =   rrsort( 'SRV', 'priority', $reply->answer );
+    die "DNS SRV query returned no SRV records. Stopped"
+        if not @answer;
+    my  %protolookup        =   (
+        '_rpc'              =>  'http',
+        '_rpcs'             =>  'https',
+    );
+    @endpoints              =   ();
+    foreach my $answer ( @answer )
+    {
+        my      $targetname =   $answer->target;
+        info "Publicly open endpoint found at %s.\n", $targetname;
+        my      $r_a        =   $res->query( $targetname, 'A'    );
+        my      $r_aaaa     =   $res->query( $targetname, 'AAAA' );
+        my      @addresses  =   ();
+        push @addresses, rrsort( 'A',    $r_a->answer )
+            if $r_a;
+        push @addresses, rrsort( 'AAAA', $r_aaaa->answer )
+            if $r_aaaa;
+        warn "No address found for $targetname,"
+            if not @addresses;
+        my      $label1     =   (split( /\./, $fqdn, 2 ))[0];
+        foreach my $address_rr ( @addresses )
+        {
+            next
+                if not exists $protolookup{$label1};
+            my $proto       =   $protolookup{$label1};
+            push @endpoints, sprintf(
+                '%s://%s:%s',
+                $proto,
+                $address_rr->address,
+                $answer->port
+            );
+        }
+    }
+    info "Preferring endpoint at %s\n", $endpoints[0];
+}
+
+sub     getblock
+{
+    my      $blockid        =   $_[0];
+    my      $json;
+    $blockid                =   Math::BigInt->new( $blockid )
+        if ref($blockid) eq ''
+        and $blockid =~ /^\d+$/;
+    if ( ref $blockid eq 'Math::BigInt' or $blockid =~ /^(earliest|latest|pending)$/ )
+    {
+        my  $specific       =  ref $blockid eq 'Math::BigInt'
+                            ?  $blockid->as_hex
+                            :  $blockid;
+        $json               =   rpcreq(
+            $endpoints[0],
+            'eth_getBlockByNumber',
+            $specific, 'false' );
+    }
+    else
+    {
+        $sth_selectBlockByHash->execute( $blockid );
+        return if $sth_selectBlockByHash->fetch;
+        $json               =   rpcreq(
+            $endpoints[0],
+            'eth_getBlockByHash',
+            $blockid, 'false'
+        );
+    }
+    die "We should have received some kind of JSON from our RPC call, " .
+        "but apparently not. Stopped "
+        if not defined $json;
+    $sth_selectBlockByHash->execute( $json->{'hash'} );
+    return if $sth_selectBlockByHash->fetch;
+    foreach my $key (qw(
+        timestamp gasUsed gasLimit difficulty number totalDifficulty size
+    )) {
+        $json->{$key}       =   Math::BigInt->new( $json->{$key} )->bstr
+            if exists $json->{$key};
+    }
+    info "B %s %s %s %s %s %s/%s\n",
+        shorthash $json->{'hash'},
+        astime($json->{'timestamp'}),
+        $json->{'number'},
+        $json->{'difficulty'},
+        $json->{'size'},
+        $json->{'gasUsed'},
+        $json->{'gasLimit'};
+    $sth_insertBlock->execute(
+        lc $json->{'hash'},
+        lc $json->{'parentHash'},
+        $json->{'number'},
+        $json->{'timestamp'},
+        $json->{'difficulty'},
+        $json->{'gasUsed'},
+        $json->{'gasLimit'},
+        $json->{'size'}
+    );
+    if ( scalar @{$json->{'transactions'}} )
+    {
+        foreach my $txhash ( @{ $json->{'transactions'} } )
+        {
+            getTransByHash( $txhash );
+        }
+    }
+    unshift @blockqueue, lc $json->{'parentHash'}
+        if $json->{'number'} != 0;
+}
+
+sub     getTransByHash
+{
+    my  $hash               =   $_[0];
+    $sth_selectTransactionByHash->execute( $hash );
+    return if $sth_selectTransactionByHash->fetch;
+    my  $json               =   rpcreq(
+        $endpoints[0],
+        'eth_getTransactionByHash',
+        $hash
+    );
+    my  $max                    =   0;
+    my  $rcpt                   =   undef;
+    do {
+        $rcpt                   =   rpcreq(
+            $endpoints[0],
+            'eth_getTransactionReceipt',
+            $hash
+        );
+        if ( not $rcpt )
+        {
+            sleep 1;
+            warn "Couldn't get the transaction receipt for $hash\n";
+        }
+    } while not $rcpt and $max++ < 60;
+    die "Couldn't get the transaction receipt for $hash\n" if not $rcpt;
+    $json->{'status'}       =   $rcpt->{'status'};
+    $json->{'gasUsed'}      =   $rcpt->{'gasUsed'};
+    $json->{'contractAddress'}
+                            =   $rcpt->{'contractAddress'};
+    foreach my $key (qw( nonce value gas gasPrice gasUsed status))
+    {
+        $json->{$key}       =   Math::BigInt->new( $json->{$key} )->bstr;
+    }
+    info    "T  %s %s %s %s -> %s\n",
+            shorthash $json->{'hash'},
+            $json->{'nonce'},
+            $json->{'value'},
+            shorthash $json->{'from'},
+            shorthash $json->{'to'};
+    my      $input          =   $json->{'input'};
+    $input                  =~  s/^0x//;
+    my      $inputlen       =   length($input) / 2;
+    my      @args           =   (
+        lc $json->{'hash'},
+        lc $json->{'blockHash'},
+        $json->{'nonce'},
+        $json->{'gas'},
+        $json->{'gasPrice'},
+        $json->{'value'},
+        lc $json->{'from'},
+        lc $json->{'to'},
+        $inputlen,
+        $json->{'gasUsed'},
+        $json->{'status'},
+    );
+    my  $function           =   $sth_insertTransaction;
+    if ( $json->{'contractAddress'} )
+    {
+        push @args, $json->{'contractAddress'};
+        $function           =   $sth_insertTransactionWithContractAddress;
+    }
+    $function->execute( @args );
+}
+
+sub     getTransRcptByHash
+{
+    my  $hash               =   $_[0];
+}
+
+sub     versioncheck
+{
+    my      $n;
+    my      $ok             =   1;
+    $n                      =   Math::BigInt->new(
+        rpcreq( $endpoints[0], "net_version" )
+    );
+    if ( $n->bcmp( $netversion ) != 0 )
+    {
+        warn "Network says it has net.version "
+            . $n->bstr
+            . " ("
+            . $n->as_hex
+            . "). Expected $netversion ("
+            . $netversion->as_hex
+            . ").\n";
+        $ok                 =   0;
+    }
+    $n                      =   Math::BigInt->new(
+        rpcreq( $endpoints[0], "eth_chainId" )
+    );
+    if ( $n->bcmp( $chainid ) != 0 )
+    {
+        warn sprintf("Network says it has eth.chainId %s (%s). Expected %s (%s).\n", $n->bstr, $n->as_hex, $chainid->bstr, $chainid->as_hex);
+        $ok                 =   0;
+    }
+    exit 1 if not $ok;
+    return $ok;
+}
+
+sub     sealer
+{
+    my      $hash           =   $_[0];
+    my      $row;
+    die unless defined $hash;
+    return $sealers{$hash} if exists $sealers{$hash};
+    $sth_selectsealer->execute( $hash );
+    return $sealers{$hash} = $row->[0] if $row = $sth_selectsealer->fetch;
+    $sth_insertsealer->execute( $hash );
+    $sth_selectsealer->execute( $hash );
+    die     "Apparently we failed to get find/create a new sealer. Stopped"
+        if not $row = $sth_selectsealer->fetch;
+    return $sealers{$hash} = $row->[0];
+}
+
+sub     getsnap
+{
+    my      $number         =   $_[0];
+    my      @blocks;
+    $number                 =   Math::BigInt->new( $number )
+        if ref($number) ne 'Math::BigInt';
+    my      $json           =   rpcreq(
+        $endpoints[0],
+        'clique_getSnapshot',
+        $number->as_hex
+    );
+    return if not defined $json;
+    my      $recents        =   $json->{'recents'};
+    return if not defined $recents;
+    my      $hash           =   $json->{'hash'};
+    my      $count          =   0;
+    while ( exists $recents->{$number->bstr} )
+    {
+        push    @blocks, $number->bstr;
+        $sth_selectBlockByHash->execute( $hash );
+        my  $row            =   $sth_selectBlockByHash->fetchrow_hashref;
+        return if not defined $row;
+        my  $newnumber      =   Math::BigInt->new( $row->{'number'} );
+        return if $newnumber->bcmp($number) != 0;
+        # Stop if we got to a block with sealers.
+        return if defined $row->{'sealer'};
+        my  $sealerhash     =   $recents->{$number->bstr};
+        return if not defined $sealerhash;
+        my  $internalid     =   sealer( $sealerhash );
+        return if not defined $internalid;
+        my $rows_affected   =   $sth_updatewhosealed->execute(
+            $internalid,
+            $hash
+        );
+        # For next block...
+        $hash               =   $row->{'parentHash'};
+        $number->bsub(1);
+    }
+    info    "S Snapshot at block # @blocks.\n";
+    return  $number;
+}
+
+sub     allsnaps
+{
+    $sth_selectmaxunknownsigned->execute();
+    my      $unkn           =   $sth_selectmaxunknownsigned->fetchrow_hashref;
+    my      $number         =   Math::BigInt->new( $unkn->{'number'} );
+    my      $committer      =   0;
+    while ( defined $number and not $number->is_zero )
+    {
+        $number             =   getsnap( $number );
+        $dbh->commit
+            if ( $committer++ % 75 ) == 0;
+    }
+    $dbh->commit;
+}
+
+sub     main
+{
+    $|                      =   1;
+    info                        "Connecting to database and setting up "
+                            .   "prepared statements.\n";
+    sql::setup;
+    #
+    info                        "Looking for public RPC endpoints of "
+                            .   "the BFA.\n";
+    $ua                     =   LWP::UserAgent->new( keep_alive => 10 );
+    endpoint_find();
+    versioncheck();
+    info                        "Looking for orphaned blocks in the "
+                            .   "database.\n";
+    push @blockqueue, sql::listOrphans();
+    
+    while ( --$maxruns != 0 )
+    {
+        unshift @blockqueue, "latest";
+        while ( @blockqueue )
+        {
+            my  $maxinarow      =   2500;
+            getblock( shift @blockqueue )
+                while @blockqueue and --$maxinarow;
+            # Find out who signed all blocks
+            allsnaps();
+            $dbh->commit;
+        }
+        sleep 5;
+    }
+}
+
+main();
diff --git a/collector/sql.pm b/collector/sql.pm
new file mode 100644
index 0000000..3c84935
--- /dev/null
+++ b/collector/sql.pm
@@ -0,0 +1,108 @@
+# 2020 Robert Martin-Legene <robert@martin-legene.dk> / <robert@nic.ar>
+# (c)2020 Secretaría Legal y Técnica de la Presidencia de la Nación
+# LGPLv2-only
+
+package sql;
+use     DBI;
+
+my      @blockqueue;
+my      $dbh;
+my      $sth_insertBlock;
+my      $sth_selectBlockByHash;
+my      $sth_selectTransactionByHash;
+my      $sth_insertTransaction;
+my      $sth_insertTransactionWithContractAddress;
+my      $sth_insertsealer;
+my      $sth_selectsealer;
+my      $sth_updatewhosealed;
+my      $sth_selectmaxunknownsigned;
+
+sub     setup
+{
+    $dbh                    =   DBI->connect(
+        'dbi:Pg:dbname=postgres;host=postgres','postgres','onlythelonely',
+        {AutoCommit=>0,RaiseError=>1}
+    )
+        or die                      DBI::errstr;
+    $sth_insertBlock        =   $dbh->prepare(q(
+        INSERT INTO blocks
+        (   hash,       parentHash, number,     timestamp,
+            difficulty, gasUsed,    gasLimit,   size        )
+        VALUES  (?,?,?,?,?,?,?,?)
+    )) or die $DBI::errstr;
+    $sth_selectBlockByHash  =   $dbh->prepare(q(
+        SELECT  parenthash,number,sealer
+        FROM    blocks
+        WHERE   hash=?
+    )) or die $DBI::errstr;
+    $sth_selectTransactionByHash
+                            =   $dbh->prepare(q(
+        SELECT  "exists"
+        FROM    transactions
+        WHERE   hash=?
+    )) or die $DBI::errstr;
+    $sth_insertTransactionWithContractAddress
+                            =   $dbh->prepare(q(
+        INSERT INTO transactions
+        (   hash,       blockHash,  nonce,  gas,
+            gasPrice,   value,      _from,  _to,
+            inputlen,   gasUsed,    status, contractAddress )
+        VALUES  (?,?,?,?,?,?,?,?,?,?,?,?)
+    )) or die $DBI::errstr;
+    $sth_insertTransaction  =   $dbh->prepare(q(
+        INSERT INTO transactions
+        (   hash,       blockHash,  nonce,  gas,
+            gasPrice,   value,      _from,  _to,
+            inputlen,   gasUsed,    status                  )
+        VALUES  (?,?,?,?,?,?,?,?,?,?,?)
+    )) or die $DBI::errstr;
+    $sth_selectsealer       =   $dbh->prepare(q(
+        SELECT  internalid
+        FROM    sealers
+        WHERE   hash=?
+    )) or die $DBI::errstr;
+    $sth_insertsealer       =   $dbh->prepare(q(
+        INSERT INTO sealers
+        (   hash    )
+        VALUES  (?)
+    )) or die $DBI::errstr;
+    $sth_updatewhosealed    =   $dbh->prepare(q(
+        UPDATE  blocks
+        SET     sealer=?
+        WHERE   hash=?
+        AND     sealer IS NULL
+    )) or die $DBI::errstr;
+    $sth_selectmaxunknownsigned
+                            =   $dbh->prepare(q(
+        SELECT  number
+        FROM    blocks
+        WHERE   number = (
+            SELECT  MAX(number)
+            FROM    blocks
+            WHERE   sealer IS NULL
+        )
+    )) or die $DBI::errstr;
+}
+
+sub listOrphans
+{
+    my      @results;
+    my      $sth_selectOrphans
+                            =   $dbh->prepare(q(
+        SELECT  parentHash,number
+        FROM    blocks
+        WHERE   parentHash NOT IN (
+            SELECT  hash
+            FROM    blocks
+        )
+    )) or die $DBI::errstr;
+    $sth_selectOrphans->execute();
+    while ( my $row         =   $sth_selectOrphans->fetch )
+    {
+        # Block 0 has it's parent listed as 0x0{64}
+        next if $row->[0] =~ /^0x0{64}$/;
+        push @results, $row->[0]
+    }
+}
+
+1;
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..c5b38b4
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,33 @@
+
+
+## POSTGRES SUPERUSER user name is postgres
+## POSTGRES SUPERUSER password is onlythelonely
+## POSTGRES USER dgsi has password bfa
+# defined in postgres/90-add-db-user.sh
+
+version: '3.4'
+
+services:
+
+  postgres:
+    build: postgres
+    restart: always
+    environment:
+      POSTGRES_PASSWORD: onlythelonely
+    volumes:
+      - blockdb_postgres:/var/lib/postgresql
+
+  collector:
+    build: collector
+    restart: always
+    environment:
+      POSTGRES_PASSWORD: onlythelonely
+
+  adminer:
+    image: adminer
+    restart: always
+    ports:
+      - 8080:8080
+
+volumes:
+  blockdb_postgres:
diff --git a/postgres/10-postgres.sql b/postgres/10-postgres.sql
new file mode 100644
index 0000000..4d31f18
--- /dev/null
+++ b/postgres/10-postgres.sql
@@ -0,0 +1,97 @@
+-- Adminer 4.7.7 PostgreSQL dump
+
+\connect "postgres";
+
+CREATE SEQUENCE account_id_seq INCREMENT 1 MINVALUE 1 MAXVALUE 2147483647 START 28 CACHE 1;
+CREATE TABLE "public"."account" (
+    "id" integer DEFAULT nextval('account_id_seq') NOT NULL,
+    "address" character(42) NOT NULL,
+    "shortname" character varying(16),
+    "name" character varying(255),
+    CONSTRAINT "account_id" PRIMARY KEY ("id"),
+    CONSTRAINT "account_hash" UNIQUE ("address")
+) WITH (oids = false);
+
+CREATE SEQUENCE blockhash_id_seq INCREMENT 1 MINVALUE 1 MAXVALUE 2147483647 START 1234567 CACHE 1;
+CREATE TABLE "public"."blockhash" (
+    "id" integer DEFAULT nextval('blockhash_id_seq') NOT NULL,
+    "hash" character(66) NOT NULL,
+    CONSTRAINT "blockhash_id" PRIMARY KEY ("id"),
+    CONSTRAINT "blockhash_hash" UNIQUE ("hash")
+) WITH (oids = false);
+
+CREATE TABLE "public"."block" (
+    "id" integer NOT NULL,
+    "parentBlockhashId" integer,
+    "number" integer NOT NULL,
+    "sealerAccountId" integer,
+    "timestamp" integer NOT NULL,
+    "difficulty" smallint NOT NULL,
+    "gasUsed" integer NOT NULL,
+    "gasLimit" integer NOT NULL,
+    "size" integer NOT NULL,
+    CONSTRAINT "block_id" PRIMARY KEY ("id"),
+    CONSTRAINT "block_sealerAccountId_fkey" FOREIGN KEY ("sealerAccountId") REFERENCES account(id) NOT DEFERRABLE,
+    CONSTRAINT "block_id_fkey" FOREIGN KEY (id) REFERENCES blockhash(id) NOT DEFERRABLE,
+    CONSTRAINT "block_parentBlockhashId_fkey" FOREIGN KEY ("parentBlockhashId") REFERENCES blockhash(id) NOT DEFERRABLE
+) WITH (oids = false);
+CREATE INDEX "block_parentBlockHashId" ON "public"."block" USING btree ("parentBlockhashId");
+CREATE INDEX "block_sealerAccountId" ON "public"."block" USING btree ("sealerAccountId");
+CREATE INDEX "block_number" ON "public"."block" USING btree ("number");
+CREATE INDEX "block_timestamp" ON "public"."block" USING btree ("timestamp");
+
+CREATE TABLE "public"."transaction" (
+    "hash" character(66) NOT NULL,
+    "blockId" integer,
+    "nonce" integer NOT NULL,
+    "gas" integer NOT NULL,
+    "gasPrice" integer NOT NULL,
+    "value" integer NOT NULL,
+    "fromAccountId" integer NOT NULL,
+    "toAccountId" integer NOT NULL,
+    "contractaddressAccountId" integer,
+    "status" smallint NOT NULL,
+    "gasUsed" integer NOT NULL,
+    "inputlen" smallint NOT NULL,
+    "input" text NOT NULL,
+    CONSTRAINT "transaction_contractaddressAccountId_fkey" FOREIGN KEY ("contractaddressAccountId") REFERENCES account(id) NOT DEFERRABLE,
+    CONSTRAINT "transaction_fromAccountId_fkey" FOREIGN KEY ("fromAccountId") REFERENCES account(id) NOT DEFERRABLE,
+    CONSTRAINT "transaction_toAccountId_fkey" FOREIGN KEY ("toAccountId") REFERENCES account(id) NOT DEFERRABLE,
+    CONSTRAINT "transaction_blockId_fkey" FOREIGN KEY ("blockId") REFERENCES block(id) NOT DEFERRABLE
+) WITH (oids = false);
+CREATE INDEX "transaction_contractaddressAccountId" ON "public"."transaction" USING btree ("contractaddressAccountId");
+CREATE INDEX "transaction_fromAccountId" ON "public"."transaction" USING btree ("fromAccountId");
+CREATE INDEX "transaction_toAccountId" ON "public"."transaction" USING btree ("toAccountId");
+CREATE INDEX "transaction_blockId" ON "public"."transaction" USING btree ("blockId");
+
+INSERT INTO "account" ("id", "address", "shortname", "name") VALUES
+(3,	'0x354779914a94ad428d2b53ae96cce3010bb0ce1e',	'RedLink',	'RedLink SA'),
+(1,	'0x377ab0cd00744dbb07b369cd5c0872dcd362c8f0',	'UNER',	'Universidad Nacional de Entre Rios'),
+(2,	'0x2feb6a8876bd9e2116b47834b977506a08ea77bd',	'PNA',	'Prefectura Nacional Argentina'),
+(5,	'0xd1f17aa41354d58940c300ffd79a200944dda2df',	NULL,	'Marandu'),
+(4,	'0x998c2651db6f76ca568c0071667d265bcc1b1e98',	NULL,	'ASI'),
+(6,	'0x39170a1ce03729d141dfaf8077c08b72c9cfdd0c',	'IXPBB',	'IXP Bahia Blanca'),
+(7,	'0x02665f10cb7b93b4491ac9594d188ef2973c310a',	'CABASE-MZA',	'CABASE Mendoza'),
+(8,	'0x19fe7b9b3a1bebde77c5374c8e13c623e3d1b5b2',	'ARIU',	'Asociación Redes de Interconexión Universitaria'),
+(9,	'0xe70fbc9d6be2fe509e4de7317637d8ee83d4f13c',	'CABASE-PMY',	'CABASE Puerto Madryn'),
+(10,	'0xe191ac3108cb2c5d70d0e978876c048d4ba41b03',	'ANSV',	'Agencia Nacional de Seguridad Vial'),
+(11,	'0xf36475eb25ba0c825455f150b26e24ab9449a443',	'SRT',	'Superintendencia de Riesgos del Trabajo'),
+(12,	'0xd1420aa9dd092f50f68913e9e53b378a68e76ed7',	'SMGP/OPTIC',	'Secretaría de Modernización de la Gestión Pública / Oficina Provincial de Tecnologías de la Información y la Comunicación - Gobierno de la Provincia del Neuquén'),
+(13,	'0x2388d2cdb2cd6e7722b4af39c3bb406dd31f560e',	'UNR',	'Universidad Nacional de Rosario'),
+(14,	'0x342e1d075d820ed3f9d9a05967ec4055ab23fa1e',	'CABASE',	'CABASE CABA'),
+(15,	'0xb3d1209aefbe00c78b2247656e2ddfa9e3897526',	'Colescriba',	'Colegio de Escribanos de la Provincia de Buenos Aires'),
+(16,	'0xa14152753515674ae47453bea5e155a20c4ebabc',	'UP',	'Universidad de Palermo'),
+(17,	'0x97a47d718eab9d660b10de08ef42bd7fd915b783',	'UNLP',	'Universidad Nacional de La Plata'),
+(18,	'0x850b30dc584b39275a7ddcaf74a5c0e211523a30',	'UM',	'Ultima Milla'),
+(19,	'0x609043ebde4a06bd28a1de238848e8f82cca9c23',	'UNSJ',	'Universidad Nacional de San Juan'),
+(20,	'0xb43b53af0db2c3fac788195f4b4dcf2b3d72aa44',	NULL,	'IPlan'),
+(21,	'0x46991ada2a2544468eb3673524641bf293f23ccc',	'UNC',	'Universidad Nacional de Cordoba'),
+(22,	'0x401d7a8432caa1025d5f093276cc6ec957b87c00',	'ONTI',	'Oficina Nacional de Tecnologias de Informacion'),
+(23,	'0x91c055c6478bd0ad6d19bcb58f5e7ca7b04e67f1',	'DGSI',	'Dirección General de Sistemas Informáticos'),
+(24,	'0x52f8a89484947cd29903b6f52ec6beda69965e38',	'CABASE-PSS',	'CABASE Posadas'),
+(25,	'0x9b3ac6719b02ec7bb4820ae178d31c0bbda3a4e0',	'Everis',	'Everis'),
+(26,	'0x99d6c9fca2a61d4ecdeb403515eb8508dc560c6b',	NULL,	NULL),
+(27,	'0xc0310a7b3b25f49b11b901a667208a3eda8d7ceb',	'',	'SyT'),
+(28,	'0xabeff859aa6b0fb206d840dbf19de970065d4437',	NULL,	'Belatrix');
+
+-- 2020-07-13 05:48:47.242334+00
diff --git a/postgres/90-add-db-user.sh b/postgres/90-add-db-user.sh
new file mode 100755
index 0000000..f734ed5
--- /dev/null
+++ b/postgres/90-add-db-user.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
+	CREATE USER leer WITH PASSWORD 'bfa';
+        GRANT CONNECT ON DATABASE postgres TO leer;
+        GRANT SELECT ON ALL TABLES IN SCHEMA public TO leer;
+        GRANT UPDATE (shortname,name) ON TABLE public.account TO leer;
+EOSQL
diff --git a/postgres/Dockerfile b/postgres/Dockerfile
new file mode 100644
index 0000000..8b8af87
--- /dev/null
+++ b/postgres/Dockerfile
@@ -0,0 +1,2 @@
+FROM postgres:13-alpine
+COPY ??-* /docker-entrypoint-initdb.d/
-- 
GitLab