Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • blockchain/nucleo
  • miguel/nucleo
  • Rcradiologia/nucleo
  • nelson/nucleo
4 results
Show changes
Commits on Source (80)
......@@ -5,12 +5,12 @@ cache
bin/env
network/node
network/bootnode
network/contracts/*
network/*.pid
test2network*/node
test2network*/bootnode
test2network*/contracts/*
test2network/*.pid
node_modules
bin/venv
src/*/*.bin
src/*/*.abi
__pycache__
LGPLv2-only
\ No newline at end of file
......@@ -20,8 +20,8 @@ Esta guía debería funcionar en Debian o sus derivados. Testeado en *Debian* y
Van a aparecer varios **warnings** mientras se instala web3. Esto parece ser "normal". Ignorarlo no parece causar problemas.
4. Cambiá al usuario `bfa`
- como root: `su - bfa`
5. Crea una cuenta (solamente nodos selladores y nodos transaccionales necesitan una cuenta)
- como bfa: `admin.sh account`
5. <del>Crea una cuenta</del> // solamente los 20-30 nodos selladores necesitan una cuenta en el nodo
- <del>como bfa: `admin.sh account`</del>
6. Comenzá la sincronización. **Esto puede llevar un rato largo** (este script se ejecuta automáticamente cuando se reinicia el sistema).
- como bfa: `start.sh`
7. `localstate.pl` muestra el estado actual del nodo.
......@@ -42,15 +42,18 @@ pero para los desarrolladores, el branch `dev` es más intersante y tambien el
### network_id
Red de producción (network):
Se puede consultar el ID de la red con el comando RPC `net.getVersion` o/y `net.version`.
Red de producción (nombre: network):
47525974938 (0xb10c4d39a)
Red de pruebas (test2network):
Red de pruebas (nombre: test2network):
55555000000 (0xcef5606c0)
### chainId
Normalmente chainId es el mismo numero que network_id, pero no es necesario. En BFA son distintos.
Se puede consultar el chainId con el comando RPC `eth.chainId`.
Red de producción (network):
200941592 (0xbfa2018)
......
#!/usr/bin/node
"use strict"
const BigNumber = require('bignumber.js');
const Libbfa = require( process.env.BFAHOME + '/bin/libbfa.js');
const rl = require('readline').createInterface(
{ input: process.stdin, output: process.stdout }
);
var web3;
var Distillery;
var bfa;
var notation = { "potency": 15 };
function init()
{
var table = [
6, 'Mwei',
9, 'Gwei',
12, 'micro',
15, 'finney',
18, 'ether',
21, 'kether',
24, 'grand',
27, 'mether',
30, 'gether',
33, 'tether'
];
for (var i = 0; table[i]; i+=2)
if ( table[i] == notation.potency )
notation.name = table[i+1]
if ( undefined == notation.name )
notation = { 'potency': 15, 'name': 'finney' };
notation.num = BigNumber( 10 ).pow( notation.potency );
bfa = new Libbfa();
web3 = bfa.newweb3();
Distillery = bfa.contract( web3, 'Distillery' );
requestDistBalance()
.then( requestBalances )
}
function palletSort(a,b)
{
if ( b == undefined )
{
if ( a == undefined )
return 0;
else
return -1;
}
if ( a == undefined )
return 1;
var strA = a.addr.toLowerCase();
var strB = b.addr.toLowerCase();
if ( strA < strB )
return -1;
if ( strA > strB )
return 1;
return 0;
}
async function requestDistBalance()
{
return web3.eth.getBalance(Distillery.contractaddress)
.then( (bal) => { return Distillery.contractbalance = new BigNumber( bal ) } )
}
async function requestBalances()
{
var count = await Distillery.methods.numberOfBeneficiaries().call();
var pallet = new Array;
var i;
// Fetch addresses from the list in the contract.
for ( i=0; i<count; i++ )
{
var addressandsetting = await Distillery.methods.atPosition(i).call();
var addr = addressandsetting[0];
var setting = addressandsetting[1];
var bal = await web3.eth.getBalance( addr );
pallet.push( { "addr": addr, "setting": new BigNumber(setting), "balance": new BigNumber(bal) } );
}
displayBalances(pallet);
}
function editAccount( entry, pallet )
{
if ( entry == undefined )
return;
var acct;
var value;
// it is an existing account address?
if ( bfa.isAddr(entry) )
{
var i = 0;
var n = pallet.length;
while ( i < n )
{
if (String(pallet[i].addr).toLowerCase() == String(entry).toLowerCase() )
entry = i;
i++;
}
}
// it is a new account address?
if ( bfa.isAddr(entry) )
{
acct = entry;
value = 0;
}
else
if ( bfa.isNumeric(entry) && entry < pallet.length )
{
acct = pallet[entry].addr;
value = pallet[entry].setting;
}
else
if ( entry == "x" )
{
var rcpt;
// trigger distribution
Distillery.methods.distribute().send( {"from": bfa.account, "gas": 4000000, gasPrice: 1000000000 } )
.on( 'error', (x) => { bfa.fatal( "Distribute returned errors: " + x ) } )
.on( 'confirmation', (n,x) =>
{
if ( undefined == rcpt )
{
rcpt = x;
console.log(
"Distribute returned succesfully in block# "
+ rcpt.blockNumber
+ " using "
+ rcpt.gasUsed
+ " gas."
);
var beforeBal = Distillery.contractbalance;
requestDistBalance()
.then(
function()
{
console.log(
"Distributed "
+ beforeBal.minus( Distillery.contractbalance ).div( notation.num ).toFixed()
+ " "
+ notation.name
+ "."
);
requestBalances();
}
)
}
}
);
return;
}
else
if ( entry == "" )
{
// Do nothing, basically just update the display
return;
}
else
{
bfa.fatal("I don't know what to do with \""+entry+"\"." );
}
rl.question(
"Adjust the "
+ notation.name
+ " fill value of "
+ acct
+ " (setting to 0 is the same as deleting)\n"
+ "Amount?: ",
(answer) => {
if ( bfa.isNumeric(answer) )
{
var bi = BigNumber( answer ).multipliedBy( notation.num );
console.log("Sending update to the SC...");
var rcpt;
Distillery.methods.setEtherAllowance(acct,web3.utils.toHex(bi))
.send( {"from": bfa.account, gasPrice: 1000000000, gas: 100000 } )
.on( 'error', (err) => {
bfa.fatal(
"\nMaybe you are not authorized:\n"
+err
+"\n\n\nI think you should leave now.\n"
);
}
)
.on( 'confirmation', (n,x) => {
if ( undefined == rcpt )
{
rcpt = x;
console.log("Update accepted.")
requestBalances();
}
}
);
}
else
bfa.fatal( "I have no idea what to do with \""+answer+"\"." );
rl.close;
}
);
}
function displayBalances( pallet )
{
var n = pallet.length;
var i;
pallet.sort(palletSort);
console.log(
"The contract's account ("
+ Distillery.contractaddress
+ ") has "
+ Distillery.contractbalance.div( notation.num ).toFixed()
+ " "
+ notation.name
+ ".\n"
);
var longest = 1;
for ( i=0; i<n; i++ )
{
var len = pallet[i].setting.div(notation.num).toFixed(notation.potency).length;
if ( len > longest )
longest = len;
}
for ( i=0; i<n; i++ )
{
var entry = pallet[i];
if ( entry == undefined )
console.log( i+": <undef>" );
else
{
var numstr = pallet[i].setting.div(notation.num).toFixed(notation.potency);
while ( numstr.length < longest )
numstr = " "+numstr;
console.log(
i
+ ": "
+ pallet[i].addr
+ " fills to "
+ numstr
+ " "
+ notation.name
+ " (has "
+ pallet[i].balance.div(notation.num).toFixed(notation.potency)
+ ")."
);
}
}
console.log("\n[ Q=quit x=distribute ]");
rl.question("Which account to edit (enter index number of full account number)?: ",
(answer) => {
if ( answer != undefined )
{
if ( String(answer).toUpperCase() == 'Q' )
process.exit( 0 );
else
editAccount(answer, pallet);
}
rl.close;
}
)
}
init();
#!/usr/bin/python3
import sys
import os
if not os.environ.get('BFAHOME'):
print("$BFAHOME not set. Did you source bfa/bin/env ?", file=sys.stderr)
exit(1)
import json
import re
import decimal
import argparse
sys.path.append( os.path.join(os.environ['BFAHOME'],'bin' ))
import libbfa
bfa = None
notation = dict()
janitor = None
distillery = None
def distbalance() -> int:
return bfa.w3.eth.getBalance(distillery.address)
def distribute():
# trigger distribution
beforeBal = distbalance()
rcpt = janitor.transact( web3=bfa.w3, function=distillery.functions.distribute, extragas=4000000)
print('Distribute returned succesfully in block# {} using {} gas.'.format(rcpt.blockNumber, rcpt.gasUsed))
afterBal = distbalance()
print('Distributed {0:,.0f} Gwei.'.format((beforeBal - afterBal)/(10**9)))
def editAccount(entry:str, beneflist:list):
acct = None
# is it an account address?
if entry == '':
# Do nothing, basically just update the display
pass
elif re.search('^0x[0-9a-fA-F]{40}$', entry):
acct = entry.lower()
# is it a known account address?
elif re.search('^[0-9]+$', entry) and int(entry) < len(beneflist):
acct = beneflist[int(entry)].addr
elif entry == 'x':
distribute()
else:
print('I do not know what to do with "{}".'.format(entry), file=sys.stderr)
exit(1)
if acct is None:
return
answer = input('Adjust the {} fill value of {} (setting to 0 is the same as deleting)\nAmount?: '.format(notation['name'], acct))
if re.search('^[0-9\.]+$', answer) is None:
print('I have no idea what to do with "{}".'.format(answer), file=sys.stderr)
exit(1)
print('Sending update to the SC...')
weilimit = float(answer) * int(notation['num'])
rcpt = janitor.transact( bfa.w3.toChecksumAddress(acct), int(weilimit), web3=bfa.w3, function=distillery.functions.setEtherAllowance)
if rcpt.status:
print('Update accepted.')
else:
print('Update failed.')
def getBeneficiaries() -> list:
count = distillery.functions.numberOfBeneficiaries().call()
beneflist = list()
# Fetch addresses from the list in the contract.
for i in range(count):
print("Indexing accounts ({}/{})...\x1B[J\r".format(i,count), end='')
(addr,topuplimit) = distillery.functions.atPosition(i).call()
bal = bfa.w3.eth.getBalance( addr )
beneflist.append( { "addr": addr, "topuplimit": topuplimit, "balance": bal } )
print("\r\x1B[J".format(i,count), end='')
s = lambda x:x['addr'].lower()
beneflist.sort(key=s)
return beneflist
def print_beneficiary_list(beneflist:list):
# find the length of the longest number-string
longestlimit = 1
longestbalance = 1
numformat = notation['strformat'].format
for i in range(len(beneflist)):
entry = beneflist[i]
numstr = numformat(decimal.Decimal(entry['topuplimit']) / notation['num'])
thislen = len(numstr)
if thislen > longestlimit:
longestlimit = thislen
numstr = numformat(decimal.Decimal(entry['balance']) / notation['num'])
thislen = len(numstr)
if thislen > longestbalance:
longestbalance = thislen
# print them all
theformat = '{:' + str(len(str(len(beneflist)-1))) + \
'}: {} fills to {:' + \
str(longestlimit) + \
'.' + \
str(notation['potency']) + \
'f} {} (has {:' + \
str(longestbalance) + \
'.' + \
str(notation['potency']) + \
'f}).'
for i in range(len(beneflist)):
entry = beneflist[i]
numstr = numformat(decimal.Decimal(entry['topuplimit']) / notation['num'])
while len(numstr) < longestlimit:
numstr = ' ' + numstr
print(theformat.format(
i,
bfa.w3.toChecksumAddress(entry['addr']),
decimal.Decimal(entry['topuplimit'])/notation['num'],
notation['name'],
decimal.Decimal(entry['balance']) / notation['num']
))
def overview():
while True:
print( "The contract's account ({}) has {} {}.".format(
distillery.address,
int(decimal.Decimal(distbalance()) / notation['num']),
notation['name']
))
beneflist = getBeneficiaries()
print_beneficiary_list(beneflist)
answer = input("\n[ Q=quit x=distribute ]\n" +
"Which account to edit (enter index number or full account number)?: ")
if answer is None or answer.upper() == 'Q':
exit( 0 )
editAccount(answer, beneflist)
def init(**kwargs):
global janitor, notation, distillery, bfa;
bfa = libbfa.Bfa(kwargs.get('uri'))
janitor = libbfa.Account(kwargs.get('sender_addr'))
table = ('Kwei', 'Mwei', 'Gwei', 'micro', 'finney', 'ether',
'kether', 'grand', 'mether', 'gether', 'tether')
potency = 18
notation['potency'] = potency
notation['name'] = table[int(potency/3-1)]
notation['num'] = pow(10, potency)
notation['strformat'] = '{' + ':.{}f'.format(potency) + '}'
abifile = ''
if os.getenv('BFAHOME'):
abifile = os.path.join(os.path.join(os.environ['BFAHOME'], 'network'))
if os.getenv('BFANETWORKDIR'):
abifile = os.path.join(os.environ['BFANETWORKDIR'])
abifile = os.path.join(abifile, 'contracts', 'Distillery', 'abi')
with open(abifile, 'rt', encoding='utf-8') as infile:
abitxt = infile.read()
abi = json.loads(abitxt)
distillery = bfa.w3.eth.contract(address=kwargs.get('sc_addr'), abi=abi)
parser = argparse.ArgumentParser(description="Command interface for BFA2018 distillery1.")
parser.add_argument(
'--uri',
metavar='URI',
nargs=1,
default='prod',
help='URI of node to connect to.')
parser.add_argument(
'--sender-addr',
metavar='ADDR',
nargs=1,
default='0xd15dd6dbbe722b451156a013c01b29d91e23c3d6',
help='Address of controller of the smart contract.')
parser.add_argument(
'--sc-addr',
metavar='ADDR',
nargs=1,
default='0xECB6aFF6e38dC58C4d9AaE2F7927A282bcB77AC2',
help='Address of smart contract.')
parser.add_argument(
'--distribute',
action='store_true',
help='Run distribute once and then exit.')
parsed = parser.parse_args()
init(uri=parsed.uri, sc_addr=parsed.sc_addr, sender_addr=parsed.sender_addr)
if parsed.distribute:
distribute()
else:
overview()
#!/bin/bash
# Robert Martin-Legene <robert@nic.ar>
if [ -z "${BFAHOME}" ]; then echo "\$BFAHOME not set. Did you source bfa/bin/env ?" >&2; exit 1; fi
# shellcheck disable=SC1090
source "${BFAHOME}/bin/libbfa.sh" || exit 1
declare -A commands help
TOMLCONFIGFILE="${BFANETWORKDIR}/config.full+archive+nohttp"
function register_subcommand
{
declare -g commands help
commands[$1]=$2
help[$1]=$3
}
function _usage()
{
local after c
after=''
for c in $(echo "${!commands[@]}" | sort)
do
after="${after}|$c"
done
echo "Usage: $(basename "$0") {${after:1}}" >&2
for c in $(echo "${!commands[@]}" | sort)
do
printf '%-15s %s\n' "$c" "${help[$c]}" >&2
done
trap - ERR
exit 1
}
function _max
{
test "$1" -lt "$2" && echo "$2"
echo "$1"
}
function pidsfromsigfiles
{
local pids pid file
pids=
for file in \
"${BFANETWORKDIR}/bootnode.pid" \
"${BFANETWORKDIR}/start-bootnode-loop.pid"\
"${BFANODEDIR}/monitor.pid" \
"${BFANODEDIR}/start-monitor-loop.pid" \
"${BFANODEDIR}/geth.pid" \
"${BFANODEDIR}/start-geth-loop.pid"
do
test -r "$file" || continue
pid=$(< "$file")
if ! [[ "$pid" =~ ^[0-9]+$ ]]
then
rm -f "$file"
continue
fi
if ! kill -0 "$pid" >/dev/null 2>/dev/null
then
rm -f "$file"
continue
fi
pids="$pids $pid"
done
echo "${pids# }"
}
register_subcommand 'ps' 'psprocs' 'Show processes relating to known .pid files'
function psprocs
{
pids=$(pidsfromsigfiles)
if [ -z "$pids" ]
then
echo "There are no processes of interest." >&2
return 1
fi
# shellcheck disable=SC2086
ps -p ${pids// /,}
}
register_subcommand 'top' 'topprocs' 'Show relevant processes in top.'
function topprocs
{
pids=$(pidsfromsigfiles)
if [ -z "$pids" ]
then
echo "There are no processes of interest." >&2
return 1
fi
# shellcheck disable=SC2086
local args pid
args=
# shellcheck disable=SC2086
for pid in $pids
do
args="$args -p $pid"
done
# shellcheck disable=SC2086
exec top $args
}
function sendsig
{
local signal pids
signal=$1
shift
pids="$*"
if [ -z "$pids" ]
then
return
fi
# shellcheck disable=SC2086
ps -p ${pids// /,}
echo "Sending ${signal} signal to pid $pids."
# shellcheck disable=SC2086
kill "$signal" $pids || true
}
register_subcommand 'kill' 'killbfastuff' 'Hard kill BFA background processes (BAD IDEA! Use stop instead).'
function killbfastuff
{
local pids
pids=$(pidsfromsigfiles)
if [ -z "$pids" ]
then
echo "Nothing to send signals to." >&2
return 2
fi
sendsig -KILL "$pids"
}
register_subcommand 'stop' 'graceful' 'Ask the BFA background processes to end gracefully.'
function graceful
{
local max pids
max=30
pids=$(pidsfromsigfiles)
if [ -z "$pids" ]
then
echo "Nothing to send signals to." >&2
return 1
fi
sendsig -TERM "$pids"
sleep 1
while :
do
pids=$(pidsfromsigfiles)
max=$((max - 1))
test "$max" -gt 0 || break
test -n "$pids" || break
printf '\rThese are still alive: %s\x1b[J' "$pids"
sleep 0.5
done
printf '\r\x1b[J'
if [ -n "$pids" ]
then
printf 'This/these pids is/are still running: %s\n' "$pids"
fi
}
register_subcommand 'initdb' 'initdb' 'Stop geth and reset the node to block zero (genesis).'
function initdb
{
killbfastuff || true
yes | geth --cache 0 --datadir "${BFANODEDIR}" removedb
geth --networkid "${BFANETWORKID}" --cache 0 --datadir "${BFANODEDIR}" init "${BFANETWORKDIR}/genesis.json"
}
register_subcommand 'exportdb' 'exportdb' 'Export blockchain in chunks of 1 million blocks per file.'
function exportdb
{
local million maxblocks blockstart toblock filename
million=1000000
graceful || true
maxblocks=$(bfageth --exec 'eth.blockNumber' console 2> /dev/null)
# 0 is genesis.. shouldn't dump that
blockstart=1
while [ "$blockstart" -lt "$maxblocks" ]
do
toblock=$(( blockstart + million - 1 ))
test "$toblock" -gt "$maxblocks" &&
toblock=$maxblocks
printf -v filename 'bfa2018.blocks.%09d-%09d.export.gz' "$blockstart" "$toblock"
if [ ! -e "$filename" ]
then
echo "Dumping blocks# $blockstart to $toblock to the file named $filename"
bfageth export "$filename" "$blockstart" "$toblock"
fi
blockstart=$(( toblock + 1 ))
done
}
register_subcommand 'importdb' 'importdb' 'Import blocks safely from previous block exports.'
function importdb
{
local dumpurl million blockstart continue_at haveblocks theirsize oursize
dumpurl="https://s3.wasabisys.com/bfa/blockdumps"
million=1000000
graceful || true
haveblocks=$(bfageth --exec 'eth.blockNumber' console 2> /dev/null)
blockstart=$((haveblocks/million*million+1))
while :
do
printf -v filename 'bfa2018.blocks.%09d-%09d.export.gz' "$blockstart" "$((blockstart + million - 1))"
blockstart=$((blockstart + million))
theirsize=$(curl --head "${dumpurl}/${filename}" 2>/dev/null | grep -i '^Content-Length: ' | head -1)
if [ -z "$theirsize" ]
then
break
fi
theirsize="${theirsize//*: }"
# remove trailing newline
theirsize="${theirsize%$'\r'}"
#
unset oursize continue_at
if [ -r "$filename" ]
then
oursize=$(stat -c %s "$filename")
continue_at="--continue-at $oursize"
fi
if [ "${oursize:-0}" -lt "$theirsize" ]
then
echo "Downloading $filename"
curl --fail $continue_at -o "$filename" "${dumpurl}/${filename}" || break
fi
# shellcheck disable=SC2086
bfageth import <(gzip -dc "$filename")
rm -f "$filename"
done
}
register_subcommand 'bootnode' 'admin_bootnode' 'Enable/disable the local bootnode.'
function admin_bootnode
{
keyfile="${BFANETWORKDIR}/bootnode/key"
echo "Only very few wants to actually run a boot node."
echo "If you have a keyfile for a bootnode, then you will"
echo "automatically start one, when restarting your system."
if [ -f "$keyfile" ]
then
echo "You are set up to run a boot node."
echo "Deleting your bootnode keyfile disables your bootnode."
yesno n "Do you want to delete your bootnode keyfile?"
if [ "$REPLY" = "y" ]
then
rm "$keyfile"
fi
pidfile="${BFANETWORKDIR}/bootnode/pid"
if [ -r "$pidfile" ]
then
pid=$(< "$pidfile")
if kill -0 "$pid"
then
echo "Terminating your bootnode."
kill "$(< "$pidfile")" || true
fi
fi
else
echo "You are not set up to run a boot node."
yesno n "Do you want to create a keyfile for a bootnode?"
if [ "$REPLY" = "y" ]
then
bootnode -genkey "$keyfile"
fi
echo "You can now start your bootnode by running start.sh"
fi
}
register_subcommand 'account' 'bfaaccount' 'Account manipulation.'
function bfaaccount
{
case "$1" in
'create')
exec create_account
;;
*)
echo "Usage: $0 account create" >&2
echo ' create' 'Create an extra account locally on the node.' >&2
exit 1
;;
esac
}
function create_account
{
local num filename plural
num=0
for filename in "${BFANODEDIR}"/keystore/*
do
test -f "$filename" &&
num=$(( num + 1 ))
done
if [ "$num" -gt 0 ]
then
plural=""
if [ "$num" -ne 1 ]
then
plural="s"
fi
yesno n "You already have ${num} account${plural}. Do you wish to create an extra?"
if [ "$REPLY" = "n" ]
then
return
fi
fi
geth --cache 0 --datadir "${BFANODEDIR}" --password /dev/null account new
}
register_subcommand 'truncatelog' 'truncatelog' \
'Truncate the log file. You may want to stop the background processes first.'
function truncatelog
{
true > "${BFANODEDIR}/log"
}
register_subcommand 'geth' 'bfageth' 'Start geth for BFA.'
function bfageth
{
if [ -z "$*" ]
then
echo "You should not run this command without any arguments. Start the background processes with start.sh instead." >&2
exit 1
fi
local DATACFG
# Strange how it seems to ignore this variable from the config file
DATACFG=$(sed --regexp-extended -ne '/^\[Node\]$/,/^$/{/^DataDir *=/{s/^.*= *"(..*)"$/--datadir \1/;p}}' "${TOMLCONFIGFILE}")
geth --config "${TOMLCONFIGFILE}" $DATACFG "$@"
}
function bfaadmin
{
case "$1" in
'bootnode')
admin_bootnode
;;
'account')
create_account
;;
*)
echo "Usage: $(basename "$0") {bootnode|account}"
trap '' ERR
exit 1
esac
}
register_subcommand 'tail' 'bfatail' 'tail -f on the logfile.'
function bfatail
{
exec tail -n 100 -F "${BFANODEDIR}/log"
exit 1
}
register_subcommand 'log' 'bfalog' 'Open the logfile with less(1).'
function bfalog
{
exec less "${BFANODEDIR}/log"
exit 1
}
function main
{
case "$(basename "$0")" in
'bfa')
local cmd
cmd=$1
shift || _usage
test -n "$cmd" || _usage
test -n "${commands[$cmd]}" || _usage
# shellcheck disable=SC2086
eval ${commands[$cmd]} "$@"
;;
'admin.sh')
bfaadmin "$@"
;;
'bfalog.sh')
bfatail
;;
'bfageth')
bfageth "$@"
exit 1
esac
}
main "$@"
#!/bin/bash
if [ -z "${BFAHOME}" ]; then echo "\$BFAHOME not set. Did you source bfa/bin/env ?" >&2; exit 1; fi
source ${BFAHOME}/bin/libbfa.sh || exit 1
exec geth --datadir ${BFANODEDIR} --networkid ${BFANETWORKID} "$@"
#!/bin/bash
# Robert Martin-Legene <robert@nic.ar>
if [ -z "${BFAHOME}" ]; then echo "\$BFAHOME not set. Did you source bfa/bin/env ?" >&2; exit 1; fi
source ${BFAHOME}/bin/libbfa.sh || exit 1
exec tail -n 100 -F ${BFANODEDIR}/log
bfa
\ No newline at end of file
......@@ -51,6 +51,7 @@ then
#
# To keep things neat, make sure we pull as the user owning the
# directory.
chown -R bfa:bfa ${BFAHOME}
runasownerof ${BFAHOME} git pull
exec $0 wealreadypulled
else
......
#!/usr/bin/node
// 20200828 Robert Martin-Legene
// License: GPLv2-only
// (c) Secretaria Legal y Tecnica, Presidencia De La Nacion, Argentina
// Looks for the contract you're specifying as argument.
// Tries to show you all the events that contract has ever logged on the blockchain.
// The contract's ABI must be in a text file in ${BFANETWORKDIR}/contracts/${contractaddr}/abi
const Web3 = require( "web3" );
const fs = require( "fs" );
const web3 = new Web3( "http://127.0.0.1:8545" );
function writeEvent(ev)
{
console.log("");
console.log( "Block number: " + ev.blockNumber );
console.log( "TX hash: " + ev.transactionHash );
console.log( "Event name: " + ev.event );
Object.keys( ev.returnValues ).forEach(
function writeEventValue( value )
{
// Will skip keys which are made entirely of digits.
if ( ! value.match(/^[0-9]+$/) )
{
console.log( "* " + value + ": " + ev.returnValues[value] )
}
}
);
}
function gotPastEvents( e, result )
{
// We are called a single time.
// Either success or failure.
if ( e )
{
console.error( e );
process.exit( 1 );
}
result.forEach( writeEvent );
console.log( "\n" + result.length + " events." );
}
var contractname = process.argv[2];
if ( contractname === undefined )
{
console.error( "Usage: " + process.argv[1] + " <contractname|contractaddr>" );
process.exit( 1 );
}
if ( process.env.BFANETWORKDIR === undefined )
{
console.error( "$BFANETWORKDIR must be defined" );
process.exit( 1 );
}
var pathprefix = process.env.BFANETWORKDIR + "/contracts/";
var filename;
if ( fs.existsSync( contractname ))
filename = contractname;
else
if ( fs.existsSync( contractname.toLowerCase() ))
filename = contractname.toLowerCase();
else
if ( fs.existsSync( pathprefix + contractname ))
filename = pathprefix + contractname;
else
if ( fs.existsSync( pathprefix + contractname.toLowerCase() ))
filename = pathprefix + contractname.toLowerCase();
if ( filename === undefined )
{
console.error( "Contract not found." );
process.exit( 1 );
}
var contractaddr = filename;
var dirname = "";
var idx = filename.lastIndexOf("/");
if ( idx > -1 )
{
dirname = filename.substr( 0, idx+1 );
filename = filename.substr( idx+1 )
contractaddr = filename;
}
var stats = fs.lstatSync( dirname + filename );
if ( stats.isSymbolicLink() )
{
let linkname = fs.readlinkSync( dirname + filename, { encoding: 'utf8' } );
filename = dirname + linkname;
contractaddr = linkname;
}
filename += "/abi";
var abi_str = fs.readFileSync( filename, { encoding: 'utf8' } );
var abi = JSON.parse( abi_str );
var contract = new web3.eth.Contract( abi, contractaddr );
console.log( "Address: " + contractaddr );
contract.getPastEvents( "allEvents", { fromBlock: "earliest", toBlock: "latest" }, gotPastEvents);
#!/bin/bash -x
trap "exit 1" ERR
wget https://gitlab.bfa.ar/blockchain/nucleo/raw/master/bin/installbfa.sh
chmod 755 installbfa.sh
./installbfa.sh
......@@ -6,7 +6,7 @@ NODEJSPGP=0x68576280
# for building, which we hope has more space.
NEW=/home/root/new
trap 'exit 1' ERR
trap 'echo "The installation failed." >&2 ; exit 1' ERR
set -o errtrace
# Be verbose
set -e
......@@ -28,19 +28,16 @@ function info
# Runs as the owner of the given directory, and in the given directory
function runasownerof
{
path=$1
precmd=
local where=$1 precmd=
shift 1
pushd $path > /dev/null
pushd $where > /dev/null
if [ $( stat --format=%u . ) -ne $UID ]
then
precmd="sudo --preserve-env --set-home -u $( stat --format=%U . ) PATH=${PATH}"
precmd="sudo --preserve-env --shell --set-home --user=$( stat --format=%U . ) PATH=${PATH}"
fi
unset path
${precmd} "$@"
rv=$?
local rv=$?
popd > /dev/null
unset precmd
return $rv
}
......@@ -66,56 +63,7 @@ function nodejsinstall
aptinstall nodejs
info "Installing nodejs modules (will show many warnings)"
runasownerof ${BFAHOME} npm install
}
function golanginstall
{
if [ ! -d /usr/local/go ]
then
info "Downloading package of go binaries."
mkdir -p ${NEW}
cd ${NEW}
local arch=$( uname -m )
case "${arch}" in
x86_64)
arch=amd64
;;
*)
echo "We have no recipe for how to build on your \"${arch}\" platform." >&2
exit 1
;;
esac
#Download go*.linux-${ARCH}.tar.gz from https://golang.org/dl/
golangurl=$(
curl -f https://golang.org/dl/ 2>/dev/null |
grep linux-${arch}.tar.gz |
grep downloadBox |
sed 's/.*href="//;s/".*//' |
head -1
)
name=$( basename $golangurl )
if [ -r "$name" ]
then
# If we have the download, check if it is corrupt
tar -ztf "$name" >/dev/null 2>&1 ||
rm -f "$name"
fi
if [ ! -r "$name" ]
then
curl -f -O $golangurl ||
rm -f "$name"
fi
# Integrity checking the archive
tar -xzf "$name"
info "Unpacking $name into /usr/local"
tar -C /usr/local -xzf go*.tar.gz
fi
if [ $( expand < ~bfa/.bashrc | grep -E "^PATH=.*/usr/local/go/bin" | wc -l ) -eq 0 ]
then
echo "PATH=\${PATH}:/usr/local/go/bin" >> ~bfa/.bashrc
fi
export PATH=${PATH}:/usr/local/go/bin
runasownerof ${BFAHOME} npm audit fix
}
function gethinstall
......@@ -142,14 +90,15 @@ function gethinstall
function initgenesis
{
(
test -z "${BFAHOME}"
HOME=$( echo ~bfa )
source ${BFAHOME}/bin/env
BFANETWORKDIR=${BFANETWORKDIR:-${BFAHOME}/network}
BFANODEDIR=${BFANODEDIR:-${BFANETWORKDIR}/node}
if [ ! -d "${BFANODEDIR}" -o ! -d "${BFANODEDIR}/geth/chaindata" ]
then
info "Node is not initialised. Initialising with genesis."
geth --cache 0 --datadir "${BFANODEDIR}" init "${BFANETWORKDIR}/genesis.json"
runasownerof "${BFAHOME}" geth --networkid ${BFANETWORKID} --cache 0 --datadir "${BFANODEDIR}" init "${BFANETWORKDIR}/genesis.json"
chown -R bfa:bfa ~bfa
fi
)
}
......@@ -259,11 +208,10 @@ grep -q Ubuntu /etc/issue && apt-add-repository multiverse
#
apt update
# development tools
aptinstall dirmngr apt-transport-https curl git curl build-essential sudo software-properties-common
aptinstall dirmngr apt-transport-https curl git curl build-essential sudo software-properties-common golang
aptinstall jq libjson-perl libwww-perl libclass-accessor-perl
userconfig
nodejsinstall
golanginstall
gethinstall
initgenesis
cronit
......
#!/bin/bash
trap "exit 1" ERR
set -x
source ${BFAHOME}/lib/versions
test -d ~/new || mkdir ~/new
cd ~/new
if [ -d "solidity" ]
then
cd solidity
git checkout develop
git pull
else
git clone --recursive https://github.com/ethereum/solidity.git
cd solidity
fi
git checkout ${solidity_tag}
scripts/install_deps.sh
mkdir -p build
cd build
cmake .. -DUSE_Z3=OFF
make solc
install --owner=bfa --target-directory=$( echo ~bfa )/bin solc/solc
......@@ -3,13 +3,13 @@
"use strict"
var request = require('request');
var net = require('net');
module.exports = class Libbfa
{
constructor() {
this.fs = require('fs');
this.Web3 = require('web3');
var net = require('net');
//
// BFAHOME
if ( undefined == process.env.BFAHOME )
......@@ -38,7 +38,7 @@ module.exports = class Libbfa
files.push( filename );
});
}
// found none?
// found any?
if ( files.length > 0 )
{
files.sort();
......@@ -54,10 +54,13 @@ module.exports = class Libbfa
this.socketurl = this.nodedir+'/geth.ipc'; // overwrite with newer ipc method
if ( this.sockettype == 'ipc' ) {
this.provider = new this.Web3.providers.IpcProvider( this.nodedir+'/geth.ipc', net );
this.req_url = 'http://unix:' + this.nodedir + '/geth.ipc:/';
} else if ( this.sockettype == 'ws' ) {
this.provider = new this.Web3.providers.WebsocketProvider( this.socketurl );
this.req_url = this.socketurl;
} else if ( this.sockettype == 'http') {
this.provider = new this.Web3.providers.HttpProvider( this.socketurl );
this.req_url = this.socketurl;
} else {
fatal("Unknown sockettype.");
}
......@@ -92,20 +95,25 @@ module.exports = class Libbfa
newweb3()
{
var w3 = new this.Web3( this.provider );
var req_url = this.req_url;
var _bfa = this;
// This could just remain the same number all the time.
var unneededcounter = 1;
w3.jsonify = function( opname, params )
{
var obj = {};
obj.id = unneededcounter++;
obj.jsonrpc = "2.0";
obj.method = opname;
obj.params = params;
return obj;
};
w3.rpcreq = function( opname, params, callback )
{
var extra = params.join(',');
var body = JSON.parse("{"+
'"jsonrpc":"2.0",' +
'"id":1,' +
'"method":"' + opname + '",' +
'"params":[' + extra + ']'
+"}"
);
request.post({
uri: 'http://localhost:8545',
uri: req_url,
json: true,
body: body,
body: w3.jsonify( opname, params ),
callback: function RPCresponse( err, obj )
{
var r;
......@@ -121,26 +129,77 @@ module.exports = class Libbfa
}
});
};
w3.req = function( opname, params, callback )
{
if ( _bfa.sockettype == 'ipc' )
{
w3.ipcreq( opname, params, callback );
}
else
{
w3.rpcreq( opname, params, callback );
}
}
w3.ipcreq = function( opname, params, callback )
{
var socket = net.connect( _bfa.socketurl );
var result;
var err;
socket.on("ready", () => {
// When the socket has been established.
// We create a new connection per request, because it
// is easier than reliably handling JSON object boundaries
// in a TCP stream .
// Writes out data and closes our end of the connection.
// Geth will reply and then close it's end.
socket.end( JSON.stringify( w3.jsonify(opname,params).valueOf()));
});
socket.on("data", (d) => {
try {
result = JSON.parse( d.toString() );
}
catch {
err = d.toString();
}
});
socket.on("timeout", () => {
socket.close();
});
socket.on("error", (e) => {
console.error(e);
err = e;
});
socket.on("close", () => {
if ( result === undefined )
err = 'Error undefined (close)'
else
if ( result.error && result.error.code && result.error.message )
err = 'Error ' + result.error.code + ": "+ result.error.message;
else
result = result.result;
callback( err, result );
});
}
w3.bfa = {
clique: {
getSigners: function clique_getSigners( cb )
{ w3.rpcreq( 'clique_getSigners', [], cb ) },
{ w3.req( 'clique_getSigners', [], cb ) },
},
miner: {
start: function miner_start()
{ w3.rpcreq( 'miner_start', [], function(){} ) },
{ w3.req( 'miner_start', [], function(){} ) },
stop: function miner_stop()
{ w3.rpcreq( 'miner_stop', [], function(){} ) }
{ w3.req( 'miner_stop', [], function(){} ) }
},
admin: {
peers: function admin_peers( cb )
{ w3.rpcreq( 'admin_peers', [], cb ) },
{ w3.req( 'admin_peers', [], cb ) },
addPeer: function admin_addPeer( peer )
{ w3.rpcreq( 'admin_addPeer', [ "\""+peer+"\"" ], function(){} ) }
{ w3.req( 'admin_addPeer', [ peer ], function(){} ) }
},
personal: {
listWallets: function personal_listWallets( cb )
{ w3.rpcreq( 'personal_listWallets', [], cb ) }
{ w3.req( 'personal_listWallets', [], cb ) }
}
};
if ( undefined != process.env.BFAACCOUNT ) {
......
......@@ -167,17 +167,20 @@ function bfainit
#
# BFANETWORKID
test -n "${BFANETWORKID}" || BFANETWORKID=47525974938
export BFANETWORKID
#
# BFANETWORKDIR
test -n "${BFANETWORKDIR}" || BFANETWORKDIR="${BFAHOME}/network"
mkdir -p "${BFANETWORKDIR}"
test -d "${BFANETWORKDIR}" || fatal "\$BFANETWORKDIR (\"${BFANETWORKDIR}\") not found."
export BFANETWORKDIR
#
# BFANODEDIR
test -n "$BFANODEDIR" || BFANODEDIR="${BFANETWORKDIR}/node"
export BFANODEDIR
#
# Default to IPC connections, because we have more geth modules available.
true ${BFASOCKETTYPE:=ipc}
export ${BFASOCKETTYPE:=ipc}
case "${BFASOCKETTYPE}" in
ipc)
true ${BFASOCKETURL:="ipc:${BFANODEDIR}/geth.ipc"}
......@@ -197,7 +200,7 @@ function bfainit
then
mkdir -p "${BFANODEDIR}"
echo "Node is not initialised. Initialising with genesis."
geth --config "${BFANETWORKDIR}/config.toml" --cache 0 init "${BFANETWORKDIR}/genesis.json"
geth --networkid ${BFANETWORKID} --cache 0 --datadir "${BFANODEDIR}" init "${BFANETWORKDIR}/genesis.json"
fi
}
......
# libbfa.py
## Library for Python3 to talk to your local POA node.
```python
#!/usr/bin/env python3
import libbfa
bee = '0xbee0966BdC4568726AB3d3131b02e6255e29285D'
d18 = '0xbfA2c97c3f59cc929e8fEB1aEE2Aca0B38235d18'
```
Which network do you want to connect to?
If you want to connect to an open/public node
you can specify `bfa2018` or `test2`. If you
want to connect to another (your own?) node,
you can specify the URL, or give a `Provider`
```python
bfa = libbfa.Bfa('test2')
bfa = libbfa.Bfa('http://localhost:8545')
```
Find the file locally which matches the name
mentioned in the first argument. '0x' is
removed before case-insensitive matching
is performed.
Second argument (if specified) is the password.
```python
acct = libbfa.Account(bee)
acct = libbfa.Account(bee, 'pepe')
```
Create a skeleton for the 'factory'
The name of the contract is given in the second
argument and must be a contract in the current
directory (not symlinked).
If the contract is not compiled locally already
your local docker installation is used (must have).
```python
Factory = libbfa.CompiledContract(bfa.w3, 'Majority')
```
### Deploy a new contract.
If no address is given, an
account (object) must be given, which will be used
to deploy a new instance of the contract on the
blockchain. *You usually only want to deploy a
contract one time.* (If you need multiple tries,
consider using the test-net).
If you choose to deploy a new contract, remember to
give the arguments that the contract wishes for it's
constructor **or you will get an absurd error message
about being out of gas**.
The number `86400` in this example is the desired
argument for deployment of this particular contract
that the smart contract will get passed to it's
constructor. This contract takes a `uint256`.
Once you succesfully have a deployed contract in the
network, you can find it's address in the receipt's
`.address` field. Make a note of that address,
because you must use that address for all
subsequent calls to reach that contract.
```python
newdeployment = Factory.instance(86400, account=acct)
contractaddress = newdeployment.address
print('Your contract is deployed at address ' + contractaddress)
newdeployment = None
```
Now it is deployed and we can pretend that happened
many days ago. The 4 lines above need not be repeated,
but the others do (libbfa, account and Factory setup).
Now, we'll create a new reference to the same contract.
Since our contract is deployed now, we
see how to reference the already deployed
contract.
```python
samecontract = Factory.instance(address=contractaddress)
# better variable name for later
majority=samecontract
```
call()s are free (no gas needed) and local to the node
you are working with.
They require no account, no signature, no
transaction and are almost instant.
```python
print('Council length: {}'.format(majority.functions.councilLength().call()))
print('Votes length: {}'.format(majority.functions.votesLength().call()))
print('isCouncil bee: {}'.format(majority.functions.isCouncil(bee).call()))
print('mayVote bee,d18,True: {}'.format(majority.functions.mayVote(bee,d18,True).call()))
print('mayVote bee,d18,False: {}'.format(majority.functions.mayVote(bee,d18,False).call()))
print('mayVote d18,bee,True: {}'.format(majority.functions.mayVote(d18,bee,True).call()))
```
Transactions form part of the blockchain and must be mined
by the sealers, so they take a little longer to complete.
They are signed by the sender account.
Your *kwargs* must reference a configured `web3` (probably the one from
`libbfa`), and also give a reference to the `function` that you are
going to send a transaction to.
The first arguments (all the *args*) will be sent to the function in
the smart contract, so make sure the match in type.
The function name is a funny mix between Python variable names you
have defined yourself, plus functions which have arisen from the smart
contracts ABI.
```python
r = acct.transact(d18, False, web3=bfa.w3, function=majority.functions.vote)
print(r)
```
### Error examples
<a name="argument-after-asterisk-must-be-an-iterable-not-nonetype"></a>
argument after * must be an iterable, not NoneType
```python
# Error text: argument after * must be an iterable, not NoneType
print(majorcontr.functions.councilLength.call())
# Fix
print(majorcontr.functions.councilLength().call())
```
<a name="argument-after-asterisk-must-be-an-iterable-not-nonetype"></a>
gas required exceeds allowance
```python
# Error text: ValueError: {'code': -32000, 'message': 'gas required exceeds allowance (8000000)'}
```
Tu transacción va a fallar.
import os
import sys
import subprocess
import json
import re
import web3
import web3.exceptions
import web3.middleware
import ecdsa
from Crypto.Hash import keccak
import eth_account;
class Bfa:
def __init__(self, provider=''):
if 'BFAHOME' not in os.environ:
if os.path.isdir('/home/bfa/bfa'):
os.putenv('BFAHOME', '/home/bfa/bfa')
elif 'HOME' in os.environ and os.path.isdir(os.path.join(os.environ['HOME'], 'bfa')):
os.putenv('BFAHOME', os.path.join(os.environ['HOME'], 'bfa'))
if isinstance(provider, str):
if provider in ['prod', 'bfa2018', 'network', '']:
provider = web3.HTTPProvider("http://public.bfa.ar:8545/")
elif provider in ['test2network', 'test2', 'test']:
provider = web3.HTTPProvider("http://public.test2.bfa.ar:8545/")
elif provider.startswith('http://') or provider.startswith('https://'):
provider = web3.HTTPProvider(provider)
else:
raise ValueError('I do not know how to handle that provider.')
w3 = web3.Web3(provider)
# inject POA compatibility middleware
w3.middleware_onion.inject(web3.middleware.geth_poa_middleware, layer=0)
self.w3 = w3
class Account:
def __init__(self, *args):
if len(args) == 0:
self.new()
return
accountname = None
passphrase = ''
if len(args) >= 1:
accountname = args[0]
if len(args) >= 2:
passphrase = args[1]
self.keyfile = None
self.key = None
self.unlock(accountname, passphrase)
self.nonce = 0
def __repr__(self) -> str:
return str(dict(keyfile=self.keyfile, key=str(self.key)))
def __str__(self) -> str:
return self.address
def new(self):
acct = eth_account.Account.create()
self.address = acct.address
self.key = acct.key
self.save()
# print(acct.address)
# print(acct.key.hex())
return acct
def save(self):
dir = None
if os.getenv('BFANODEDIR') and os.path.exists(os.path.join(os.environ['BFANODEDIR'], 'keystore')):
dir = os.path.join(os.environ['BFANODEDIR'], 'keystore')
elif os.getenv('BFANETWORKDIR') and os.path.exists(os.path.join(os.environ['BFANETWORKDIR'], 'node', 'keystore')):
dir = os.path.join(os.environ['BFANETWORKDIR'], 'node', 'keystore')
elif os.getenv('BFAHOME') and os.path.exists(os.path.join(os.environ['BFAHOME'], 'network', 'node', 'keystore')):
dir = os.path.join(os.environ['BFAHOME'], 'network', 'node', 'keystore')
elif os.getenv('HOME'):
dir = os.path.join(os.environ['HOME'], '.ethereum', 'keystore')
os.makedirs(dir, mode=0o700, exist_ok=True)
else:
raise OSError('I have no idea where to save the file.')
self.keyfile = os.path.join(dir, self.address)
encrypted = eth_account.Account.encrypt(self.key.hex(), '')
try:
with open(self.keyfile, 'w', encoding='utf=8') as outfile:
outfile.write(str(encrypted).replace("'", '"'))
except:
# os.remove(filename)
raise
pass
@staticmethod
def findkeyfilesindirectories(**kwargs):
pattern = ''
if 'pattern' in kwargs:
pattern = kwargs['pattern']
# Remove leading 0x if present
if pattern.startswith('0x'):
pattern = pattern[2:]
# Lower case the pattern (account name)
pattern = pattern.lower()
# Find candidate directories of where to look for accounts
wheretolook = []
if os.getenv('BFANODEDIR'):
wheretolook += [os.path.join(os.environ['BFANODEDIR'], 'keystore')]
elif os.getenv('BFANETWORKDIR'):
wheretolook += [os.path.join(os.environ['BFANETWORKDIR'], 'node', 'keystore')]
elif os.getenv('BFAHOME'):
wheretolook += [os.path.join(os.environ['BFAHOME'], 'network', 'node', 'keystore')]
if os.getenv('HOME'):
wheretolook += [
os.path.join(os.environ['HOME'], '.ethereum', 'keystore'),
os.path.join(os.environ['HOME'], '.ethereum', 'keystore', 'test2'),
os.path.join(os.environ['HOME'], '.ethereum', 'keystore', 'network')
]
# Look for our pattern (or all files) in the directories
matches = []
ourregexp = '.*{}$'.format(pattern)
for d in wheretolook:
if os.path.exists(d):
for f in os.listdir(d):
fn = os.path.join(d, f)
if os.path.isfile(fn):
if re.match(ourregexp, f.lower()):
matches += [os.path.join(d, f)]
if pattern:
if len(matches) == 0:
# if a pattern was given but no matches were found,
# return None
return None
else:
# if a pattern was found but some matches were found,
# return just the first one
return matches[0]
# If no pattern was given, return everything found, or the
# empty list.
return matches
def unlock(self, accountname: str, passphrase: str):
if os.path.isfile(accountname):
self.keyfile = accountname
else:
self.keyfile = self.findkeyfilesindirectories(pattern=accountname)
if self.keyfile is None:
raise FileNotFoundError('The account was not found.')
with open(self.keyfile) as fd:
encrypted_key = fd.read()
try:
self.key = web3.Web3().eth.account.decrypt(encrypted_key, passphrase)
except ValueError as exc:
raise ValueError(
'The passphrase given for the account in file {} is incorrect, '
'or the input file is not a valid key file.'.format(self.keyfile)
) from exc
# ADDRESS
publickey = ecdsa.SigningKey.from_string(self.key, curve=ecdsa.SECP256k1).verifying_key
pkbytestring = publickey.to_string() # returns bytestring
ourhash = keccak.new(digest_bits=256)
ourhash.update(pkbytestring)
digest = ourhash.hexdigest()
self.address = web3.Web3().toChecksumAddress('0x' + digest[-40:])
def transact(self, *args, **kwargs):
w3 = kwargs.get('web3')
tx_details = self.calculate_tx_details(w3, *args, **kwargs)
afunction = kwargs.get('function')
txobj = tx_details
if afunction:
txobj = afunction(*args).buildTransaction(tx_details)
receipt = self.txsignsendwait(w3, txobj)
return receipt
def txsignsendwait(self, w3: web3.Web3, txobj: dict):
signedobj = self.signtx(txobj)
txhashbytes = w3.eth.sendRawTransaction(signedobj.rawTransaction)
self.nonce = self.nonce + 1
receipt = w3.eth.waitForTransactionReceipt(txhashbytes)
return receipt
def signtx(self, tx: dict):
signed = web3.Web3().eth.account.sign_transaction(tx, self.key)
return signed
def calculate_tx_details(self, w3: web3.Web3, *args, **kwargs) -> dict:
# if kwargs has extragas=50000 then we add that number to the amount
# of gas for the transaction
afunction = kwargs.get('function')
# Nonce may have increased on the network without us noticing
# or past transactions may not yet have been mined (and a flooded
# txpool).
# This is a resonable fix (try not to send too many transactions)
# If you use waitForTransactionReceipt() between each transaction
# you will not have problems because of this.
self.nonce = max(self.nonce, w3.eth.getTransactionCount(self.address))
# Set minimum gasPrice to 1 Gwei, but allow more if the network says so.
details = {
'chainId': w3.eth.chain_id,
'gasPrice': min(w3.toWei('1', 'gwei'), w3.eth.gasPrice),
'nonce': self.nonce,
'from': self.address,
}
for kw in [ 'to', 'value' ]:
val = kwargs.get(kw)
if val is not None:
details[kw] = val
# Ask for balance, so we can tell it, in case we have an exception
balance = w3.eth.getBalance(self.address)
try:
if afunction:
# Ask a node how much gas it would cost to deploy
gas = afunction(*args).estimateGas(details)
else:
gas = w3.eth.estimateGas(details)
except web3.exceptions.SolidityError as exc:
raise web3.exceptions.SolidityError(
'The Ethereum Virtual Machine probably did not like that.'
) from exc
except ValueError as exc:
raise ValueError(
'Your transaction will fail. Maybe you are calling your '
'contract wrong or are not allowed to call the funcion '
'by a function modifier or '
'your account may not have enough balance to work on this '
'network. Your account balance is currently {} wei.'
.format(balance)) from exc
if kwargs.get('extragas') is not None:
gas += kwargs.get('extragas')
details['gas'] = gas
return details
class Abi(list):
def __str__(self):
txt = ''
for i in range(len(self)):
elem = self.__getitem__(i)
if type(elem) is not dict:
continue
if 'type' in elem:
txt += "({}) ".format(elem['type'])
name = ""
if 'name' in elem:
name = elem['name']
if 'inputs' in elem:
inputlist = list()
for inputnum in range(len(elem['inputs'])):
_input = elem['inputs'][inputnum]
args = _input['type']
if 'name' in _input and _input['name'] != '':
args = "{}: {}".format(_input['name'], args)
inputlist.append(args)
txt += "{}({})".format(name, ', '.join(inputlist))
if 'outputs' in elem:
outputlist = list()
for outputnum in range(len(elem['outputs'])):
output = elem['outputs'][outputnum]
if 'name' in output and output['name'] != '':
outputlist.append("{}: {}".format(output['name'], output['type']))
else:
outputlist.append("{}".format(output['type']))
txt += " -> ({})".format(', '.join(outputlist))
if 'stateMutability' in elem:
txt += ' [{}]'.format(elem['stateMutability'])
txt += "\n"
return txt
# print(txt, file=sys.stderr)
# return super(Abi, self).__str__()
class CompiledContract:
solc_features = [
'abi', 'asm', 'ast', 'bin', 'bin-runtime', 'compact-format',
'devdoc', 'generated-sources', 'generated-sources-runtime',
'hashes', 'interface', 'metadata', 'opcodes', 'srcmap',
'srcmap-runtime', 'storage-layout', 'userdoc'
]
dockerWorkdir = '/casa'
def __init__(self, w3: web3.Web3, name: str):
self.w3 = w3
self.name = name
self.json = None
self.readtextfile()
# Did read give us json, or should we compile it?
if self.json is None:
self.compile()
def dockerargs(self):
return [
'docker', 'run',
'--rm',
# Mount our cwd as /casa
'-v',
'{}:{}'.format(os.getcwd(), self.dockerWorkdir),
# Run as us inside the docker, so we have access to this directory
'-u', str(os.getuid()),
'bfaar/nodo',
'/usr/local/bin/solc',
'--evm-version', 'byzantium',
# Get as many things dumped into our JSON as possible.
# You never know when you'll need it, and space is cheap.
'--combined-json', ','.join(self.solc_features),
# We like optimized things.
'--optimize',
# File name of input file.
'{}/contract.sol'.format(self.dockerWorkdir)
]
def compile(self):
# Make a copy with a fixed name
# Mostly so people can use symlinks to the source files
# which won't work when we call docker.
candidate_sol = '{}.sol'.format(self.name)
if os.path.exists(self.name):
filename = self.name
elif os.path.exists(candidate_sol):
filename = candidate_sol
else:
filename = self.name
with open(filename, 'r') as infile:
with open('contract.sol', 'w') as outfile:
outfile.write(infile.read())
try:
solc = subprocess.run(self.dockerargs(), stdout=subprocess.PIPE, check=True)
finally:
# Don't leave too much mess.
os.remove('contract.sol')
txt = solc.stdout
output = txt.decode('utf-8')
self.json = json.loads(output)
self.writetextfile()
def readtextfile(self):
output = None
for filename in ( '{}.compiled.json'.format(self.name), self.name ):
if os.path.exists( filename ):
with open(filename, 'rt', encoding='utf-8') as infile:
output = infile.read()
break
if output is None:
print("File not found.", file=sys.stderr)
raise FileNotFoundError
if len(output) < 2:
print("The JSON file is too small ({} bytes read from {}).".format(len(output), filename), file=sys.stderr)
raise NameError
try:
self.json = json.loads(output)
except json.decoder.JSONDecodeError as exc:
print("It was not possible to parse the JSON file (from is {}).".format(filename), file=sys.stderr)
raise exc
def writetextfile(self):
filename = self.name + '.compiled.json'
try:
with open(filename, 'xt', encoding='utf-8') as outfile:
outfile.write(json.dumps(self.json))
except:
os.remove(filename)
raise
def _where(self):
# Inch our way closer and closer, all the time,
# as long as we see labels which look vaguely familiar.
# This may enable us to be more liberal in the JSON input
# we receive from the file containing the cached compiled JSON.
point = self.json
for teststring in [
'contracts',
'{}/{}.sol:{}'.format(self.dockerWorkdir, 'contract', self.name),
'{}/{}.sol:{}'.format(self.dockerWorkdir, self.name, self.name)
]:
if type(point) is dict and teststring in point:
point = point[teststring]
return point
def bytecode(self):
self._where()
return '0x' + self._where()['bin']
def abi(self):
# Old method which also works, but our own class has a nicer __str__:
# return json.loads(self.json['contracts'][where]['abi'])
where = self._where()
if type(where) is str:
where = json.loads(where)
if (type(where) is dict) and 'abi' in where:
where = where['abi']
if type(where) is str:
where = json.loads(where)
return Abi(where)
def instance(self, *args, **kwargs):
addr = kwargs.get('address')
if addr is None:
account = kwargs.get('account')
if account is None:
raise KeyError('Either address or account must be speficied, in order to get an instance.')
ourinstance = self.w3.eth.contract(abi=self.abi(), bytecode=self.bytecode())
receipt = account.transact(*args, web3=self.w3, function=ourinstance.constructor)
if receipt.status == 0:
raise SystemError('Failed to deploy.')
addr = receipt.contractAddress
return self.w3.eth.contract(address=addr, abi=self.abi())
web3
rusty-rlp
ecdsa
Crypto
dnspython
......@@ -9,10 +9,15 @@ use warnings;
use Carp;
use Math::BigInt;
use JSON;
use IO::Socket::INET;
BEGIN {
die "\$BFAHOME not set. Did you source bfa/bin/env ?\n"
unless exists $ENV{BFAHOME};
}
my %network = (
47525974938 => 'BFA red principal [est. 2018]',
55555000000 => 'BFA red de pruebas numero 2 [est. 2019]',
);
chdir $ENV{BFAHOME} or die $!;
use lib $ENV{'BFAHOME'}.'/bin';
use libbfa;
......@@ -151,8 +156,49 @@ sub rpc
my $netversion = rpc( $libbfa, 'net_version' );
if ( $netversion )
{
printf "Running on network number %s\n", $netversion;
if ( exists $network{$netversion} )
{
printf "Running on network %s (#%s)\n", $network{$netversion}, $netversion;
} else {
printf "Running on network %s\n", $netversion;
}
}
### compare time
use constant days70y => 25567;
my $s = IO::Socket::INET->new(
PeerAddr => "time.nist.gov:37",
Timeout => 3,
);
if ( defined $s )
{
$s->recv(my $data, 8);
my $i = unpack('N', $data) || 0;
if ( $i > 0 )
{
# rfc868 offset (seconds from 1900-01-01 to 1970-01-01)
$i -= 2208988800;
printf "NIST time: %s\n", scalar(localtime($i));
my $heretime = time();
printf "Here time: %s\n", scalar(localtime($i));
if ( abs($i - $heretime) > 5 )
{
print "WHY IS YOUR CLOCK OFF?";
}
}
}
### 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 );
### syncing ?
my $syncing = rpc( $libbfa, 'eth_syncing' );
if ( $syncing )
......@@ -167,31 +213,22 @@ if ( $syncing )
}
else
{
print "We are currently not syncing (this is normal if you have all the blocks).\n";
if ( $timediff > 90 )
{
print "We are currently not syncing. WHY ARE OUR BLOCKS SO OLD?\n";
} else {
print "We have all the blocks and are not syncing.\n";
}
}
### mining ?
$result = rpc( $libbfa, 'eth_mining' );
if ( $result )
{
printf "We mine when we can.\n";
printf "We are a sealer and are configured seal.\n";
} else {
print "We do not seal.\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' );
......@@ -275,8 +312,7 @@ if ( $result )
$txn =~ s/^0x([a-fA-F\d]+)$/hex($1)/e;
my $gold = rpc( $libbfa, 'eth_getBalance', qq("$account"), '"latest"' );
$gold = Math::BigInt->new( $gold ) if $gold =~ /^0x/;
#$gold = Math::BigInt->new( $gold ) if $gold =~ s/^0x([\da-fA-F]{2})/0x0000$1/;
printf "Account %d: %s %-6s %3d transaction%s, %s satoshi.\n", $i, $account, $maymine, $txn, ($txn==1?' ':'s'), $gold;
printf "Account %d: %s %-6s %3d transaction%s, %s wei.\n", $i, $account, $maymine, $txn, ($txn==1?' ':'s'), $gold;
}
}
else
......