Skip to content
Snippets Groups Projects
Commit e49e8126 authored by Robert Martin-Legene's avatar Robert Martin-Legene
Browse files

Merge branch 'master' of github.com:rlegene/bfa

parents 090f92cd 223fcc24
No related branches found
No related tags found
No related merge requests found
......@@ -24,6 +24,13 @@
- If the text can not be found, it is because your insert transaction still isn't in the blockchain. Wait a bit and try again.
- Try the basic `explorer.sh`. It follows "latest" by default, but you can specify a block number as argument, e.g. `explorer.sh 0` to see genesis (block 0).
- Try out `walker.pl`
12. Install node.js so you can do better scripts locally:
`sudo apt-key adv --keyserver keyserver.ubuntu.com --recv 68576280`
`sudo apt-add-repository "deb https://deb.nodesource.com/node_7.x $(lsb_release -sc) main"`
`sudo apt-get update`
`sudo apt-get install nodejs`
`npm init -y`
`npm install web3`
There are other "interesting" programs in the bin/ and src/ directories.
......
#!/usr/bin/node
var port = 14349; // cloud
port = 16437; // bc
const networkdir = 'network5445';
const readline = require('readline');
const rl = readline.createInterface( { input: process.stdin, output: process.stdout } );
const Web3 = require('web3');
const fs = require('fs');
const Personal = require('web3-eth-personal');
var web3;
var GasWell;
var from;
var contractbalance;
var contractaddr;
function fatal( txt )
{
console.log( txt );
process.exit( 1 );
}
function init()
{
if ( process.env.BFAHOME == undefined )
fatal( "$BFAHOME not set. Did you source bfa/bin/env ?" );
web3 = new Web3( "http://127.0.0.1:"+port );
//accounts = new Accounts( web3 );
var abi = "";
contractaddr = fs.realpathSync([ process.env.BFAHOME, networkdir, 'contracts', 'GasWell' ].join('/'));
if ( contractaddr == undefined )
fatal( "I can't seem to find the contract directory containing the Gas Well's ABI" );
contractaddr = contractaddr.replace(/^.*\//, '');
var abipath = [ process.env.BFAHOME, networkdir, 'contracts', contractaddr, 'abi' ].join('/');
var abifile = fs.createReadStream( abipath )
.on('readable', () => {
var data;
while (data = abifile.read())
abi += data;
})
.on('end', () => {
GasWell = new web3.eth.Contract( JSON.parse(abi), contractaddr );
web3.eth.getBalance( contractaddr ).then( function(v){contractbalance=v} );
getlist();
})
;
getAccount();
}
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
function isAddr(n) {
return n.length == 42 && n.substring(0,2) == "0x";
}
function getAccount()
{
var personal = new Personal( web3 );
personal.getAccounts().then(
function gotPersonalAccounts(obj) {
console.log("Setting default account to "+ obj[0]);
from = obj[0];
web3.eth.defaultAccount = obj[0];
},
function failedGettingPersonalAccounts(err){
fatal ("Did you remember to allow 'personal' via the RPC port? " + err)
}
);
}
function getpos(i)
{
var arr;
var p = GasWell.methods.atPosition(i).call()
.then(
function gotOneAccount(result) {
console.log( "CC: "+result);
console.log( "C1: "+result[0]);
arr = result;
},
function gotOneAccountError(err) {
console.log( "DD: "+err);
console.log(err);
}
);
return arr;
}
function wellSort(a,b)
{
if ( b == undefined )
{
if ( a == undefined )
return 0;
else
return -1;
}
if ( a == undefined )
return 1;
var strA = a[0].toLowerCase();
var strB = b[0].toLowerCase();
if ( strA < strB )
return -1;
if ( strA > strB )
return 1;
return 0;
}
function requestBalances( count )
{
var well = new Array;
var proms = new Array;
var i;
for ( i=0; i<count; i++ )
{
proms.push(
GasWell.methods.atPosition(i).call( {} )
.then(
function(res){
well.push(res);
},
function(err) { console.log("Fetching position data failed: "+err) }
)
);
}
Promise.all( proms ).then(
function(x) {
// The Well has now been filled out
// so we will ask for the balances of the found accounts
var p2 = new Array();
for ( i=0; i<count; i++ )
{
var cb = setBal.bind(null,well,i);
p2.push( web3.eth.getBalance( well[i][0] ).then(cb) );
}
Promise.all( p2 ).then(
function allbalances(a) { displayBalances( well ) },
function(err) { console.log("Getting balances failed: "+err) }
);
},
function(err) { console.log("Getting account list failed: "+err) }
);
}
function setBal( arr, idx, val )
{
arr[idx][2]=val;
}
function editAccount( entry, well )
{
if ( entry == undefined )
return;
var acct;
var value;
// it is an existing account address?
if ( isAddr(entry) )
{
var i = 0;
var n = well.length;
while ( i < n )
{
if ( (""+well[i][0]).toLowerCase() == (""+entry).toLowerCase() )
entry = i;
i++;
}
}
// it is a new account address?
if ( isAddr(entry) )
{
acct = entry;
value = 0;
}
else
if ( isNumeric(entry) && entry < well.length )
{
acct = well[entry][0];
value = well[entry][1];
}
else
if ( entry == "x" )
{
// trigger Gas Well distribution
GasWell.methods.distribute().send( {"from": from, "gas": 1000000 } )
.then(
function distOK(x) {
console.log("Distribute returned succesfully in block# "+x.blockNumber+" using "+x.gasUsed+" gas.");
getlist();
},
function distFail(x) {
console.log("Distribute returned errors in block# "+x.blockNumber+" using "+x.gasUsed+" gas.");
console.log(x);
process.exit( 1 );
}
);
return;
}
else
fatal("I don't know what to do with \""+entry+"\"." );
rl.question("Adjust the Mwei fill value of "+acct+" (setting to 0 is the same as deleting)\nAmount?: ", (answer) => {
if ( isNumeric(answer) )
{
answer *= 1000000;
console.log("Sending update to the Gas Well...");
GasWell.methods.setEtherAllowance(acct,answer).send( {"from": from } )
.then(
function(a){
console.log("Update accepted.")
getlist();
},
function(b){
fatal("\nMaybe you are not authorized:\n"+b+"\n\n\nI think you should leave now.\n");
}
)
;
}
else
fatal( "I have no idea what to do with \""+answer+"\"." );
rl.close;
});
}
function displayBalances( well )
{
var n = well.length;
var i;
if ( well == undefined )
fatal( "Bank is not defined." );
well.sort(wellSort);
console.log("The contract's account ("+contractaddr+") has "+Math.floor(contractbalance/1000000)+" Mwei.\n");
var longestbefore = 1;
var longestafter = 9;
for ( i=0; i<n; i++ )
{
var len = Math.floor(well[i][1]/1000000).toString().length;
if ( len > longestbefore )
longestbefore = len;
}
for ( i=0; i<n; i++ )
{
var entry = well[i];
if ( entry == undefined )
console.log( i+": <undef>" );
else
{
var numstr = ""+well[i][1]/1000000;
var before = numstr;
var after = "";
if ( numstr.search(/\./) > -1 )
{
before = numstr.replace(/\.[0-9]+$/, '');
after = numstr.replace(/^[0-9]*\./, '');
}
while ( before.length < longestbefore )
before = " "+before;
while ( after.length < longestafter )
after += "0";
numstr = before+"."+after;
var has = well[i][2].toString();
if ( has.length > 9 )
has = has.replace(/[0-9]{9}$/, ".$&");
console.log( i+": "+well[i][0]+" fills to "+numstr+" Mwei (has "+has+")." );
}
}
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 ( (""+answer).toUpperCase() == 'Q' )
process.exit( 0 );
else
editAccount(answer, well);
}
rl.close;
}
)
}
function getlist()
{
GasWell.methods.numberOfBeneficiaries()
.call()
.then(
requestBalances,
function beneficiaryFail(x){console.log(x)}
);
}
init();
#!/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
# get number of accounts
# get limit of every account
# allow scroll up/down to select an account
# press enter to edit an account's limit
# press ins to add an account
# press del to delete an account (set limit = 0)
# press Q to quit.
js=$( mktemp )
out=$( mktemp )
cleanup $js $out
yes | head -1000
function admin
{
while :
do
(
echo var contract = $( contract GasWell )
echo "var pos = contract.numberOfBeneficiaries.call();"
echo "while (pos-- > 0) { var r=contract.atPosition(pos); console.log(r[0]+':'+r[1]);}"
) > $js
cat $js
geth_attach < $js > $out
cat $out
readarray -t < $out
# Delete last entry, because that's the "return value from our call to geth".
unset MAPFILE[$(( ${#MAPFILE[*]} - 1 ))]
if [ ${#MAPFILE[*]} -gt 0 ]
then
printf "%5s %-42s %10s\n" Index "Account address" Value
echo ''
for key in ${!MAPFILE[*]}
do
val=${MAPFILE[$key]}
printf "%5d %-42s %10s\n" $key ${val%:*} ${val#*:}
done
echo
echo '["+"=New entry, Q=quit, index number]'
read -p "Change which one of these: "
else
echo "No accounts found."
REPLY="+"
fi
if [ "${REPLY^^}" = "Q" -o -z "${REPLY}" ]
then
return
elif [ "${REPLY}" = "+" ]
then
read -p "Enter new account address: " acct
if [ ${#acct} -eq 40 -a "${acct:0:2}" != "0x" ]
then
acct="0x${acct}"
fi
amount=0
elif [ "${REPLY}" != '+' ]; REPLY=$( echo ${REPLY} | sed 's/[^0-9]//' ); [ -n "${REPLY}" ]
then
if [ ${#MAPFILE[*]} -gt "${REPLY}" ]
then
amount=${MAPFILE[${REPLY}]}
acct=${amount%:*}
amount=${amount#*:}
fi
else
return
fi
echo '["DEL"=delete]'
read -i "${amount}" -p "Set value for account ${acct}: " amount
if [ "${amount^^:0:3}" = "DEL" ]
then
amount=0
fi
contractSendTx GasWell setEtherAllowance \"$acct\" ${amount} | geth_attach
done
}
admin
#void setEtherAllowance( address, uint256 )
#uint256 numberOfBeneficiaries( )
#void distribute( )
#uint256 getEtherAllowance( address )
#!/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
bfaconfig node
exec tail -n 100 -F ${BFANODEDIR}/log
#!/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
bfaconfig network
contract="${BFANETWORKDIR}/contracts/GasWell"
realdir=$( realpath "${contract}" )
test -r "${realdir}"
address=$( basename "${realdir}" )
test -n "${address}"
contractSendTx GasWell selfDestruct | tee /dev/tty | geth_attach
#!/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
bfaconfig network
contract="${BFANETWORKDIR}/contracts/GasWell"
realdir=$( realpath "${contract}" )
test -r "${realdir}"
address=$( basename "${realdir}" )
test -n "${address}"
contractSendTx GasWell distribute | tee /dev/tty | geth_attach
// vim:filetype=javascript
pragma solidity ^0.4.24;
contract GasWell {
address owner;
struct Allowances {
address beneficiary;
uint256 value;
}
Allowances[] thelist;
// We use distpos to remember where we were stopped processing last time
// we were called. The idea is, that if we have too many accounts to take
// care of, and too little gasleft, then we stop before we run out of
// gas, since that would undo all the transactions we had already handled.
// Also, we don't want to favour anyone in particular in the list, such
// that some would have first priority in getting ether at every invocation
// So we continue checking the list from the same place where we left off
// at the previous invocation of distribute()
uint256 distpos;
constructor() public payable {
owner = msg.sender;
}
modifier onlyOwner {
require( msg.sender == owner );
_;
}
// Using this function, you can find out how long thelist is.
function numberOfBeneficiaries() public view returns (uint256) {
return thelist.length;
}
// Using this function, you get the address and "value" at a given position in thelist.
function atPosition( uint256 idx ) public view returns (address,uint256) {
require( idx <= thelist.length, "There are not that many addresses in the list." );
return (thelist[idx].beneficiary,thelist[idx].value);
}
// Returns a (signed) position of where an address can be found in thelist.
// Or returns -1 if the address is not found in thelist.
function _beneficiaryPosition( address beneficiary ) internal view returns (int256) {
uint256 pos = thelist.length & 0x7fffffffffffffffffffffffffffffff;
while ( pos-- > 0 )
if ( beneficiary == thelist[pos].beneficiary )
return int256( pos & 0x7fffffffffffffffffffffffffffffff );
return -1;
}
// This function returns the "allowance" that a given address is set to.
// Using this function, you don't have to cycle through atPosition() until
// you find the address you want to know about.
function getEtherAllowance( address beneficiary ) public view returns (uint256) {
int256 pos = _beneficiaryPosition( beneficiary );
if ( pos == -1 )
return 0;
return thelist[uint256(pos)].value;
}
// This admin (ownerOnly) function allows the creator of the contract to
// add/change/delete "allowances" per address.
function setEtherAllowance( address beneficiary, uint256 value ) public onlyOwner {
int256 pos = _beneficiaryPosition( beneficiary );
if ( value > 0 ) {
if ( pos == -1 )
// Add the address to thelist if it was not already there
thelist.push( Allowances(beneficiary, value) );
else
// Simple update the value of this address
thelist[uint256(pos)].value = value;
return;
} else {
// The beneficiary is set to have 0 Ether, so we
// delete the beneficiary from the list
uint i = uint256(pos) + 1;
while ( i++ < thelist.length )
thelist[i] = thelist[i+1];
// If distpos was past the position that we removed,
// then move that back one.
if ( distpos > uint256(pos) )
distpos--;
// Shorten the list
thelist.length--;
}
}
function selfDestruct() public onlyOwner {
selfdestruct( owner );
}
function distribute() external {
uint256 listlength = thelist.length;
// Is there anything to do at all
if ( listlength == 0 )
return;
// Has the list gotten shorter since we we were last time?
// This shouldn't happen, but it's better to be safe than to be sorry.
if ( distpos >= listlength )
distpos = 0;
uint256 wheretostop = distpos;
while ( gasleft() > 54321 ) {
// Did we get to the end of the list, then start over
if ( ++distpos >= listlength )
distpos = 0;
uint256 balance = thelist[distpos].beneficiary.balance;
uint256 value = thelist[distpos].value;
if ( balance < value && (value+diff) > 0 )
{
uint256 diff = value - balance;
// we use send() instead of transfer(), because
// transfer() can throw(), and we don't want
// to stop processing because of a single error.
thelist[distpos].beneficiary.send( diff ) || true;
}
if ( wheretostop == distpos )
return;
}
}
function () external payable {
}
}
pragma solidity ^0.4.22;
// This contract is supposed to maintain the list of authorized sealers.
// For this to work, a sealer must deploy the contract and then all sealers
// must occasionally (every 5 minutes) run the related SealerSync.js
contract Sealers {
// This struct contains a list of votes and who has voted for each victim/beneficiary.
// Votes taking longer than 30K blocks, will have to restart.
struct Proposal {
address victim; // Whom are we voting about.
uint votestart; // In which block did this vote begin?
uint256 votestart; // In which block did this vote begin?
bool promotion; // true=promotion, false=demotion
address[] voters; // List of voters.
}
Proposal[] public adminproposals;
address[] public sealers;
Proposal[] public sealerproposals;
event vote( address voter, address victim, bool promotionOrDemotion );
event adminChange( address admin, bool promotionOrDemotion );
......@@ -18,107 +24,139 @@ contract Sealers {
sealers[0] = msg.sender;
}
function isSealer( address subj ) public view returns (bool) {
for (uint i=sealers.length; i>0; i--)
{
if (subj == sealers[i-1])
return true;
}
return false;
}
modifier onlySealersTX {
require( isSealer( tx.origin ), "tx.origin is not a known sealer and can not call this function." );
_;
// Public utility, works on any list needle and haystack given.
//
// Returns an index to the position of the needle inside haystack.
// 0 is first position. -1 means not found.
function findAddressInList( address[] haystack, address needle ) public pure returns (int256) {
uint256 i = haystack.length;
while ( i-- > 0 )
if ( needle == haystack[i] )
return int256(i & 0x7fffffffffffffffffffffffffffffff);
return -1;
}
modifier onlySealers {
require( isSealer( msg.sender ), "msg.sender is not a known sealer and can not call this function." );
_;
function sealerIdx( address needle ) public view returns (int256) {
return findAddressInList( sealers, needle );
}
function sealerLength() public view returns (uint) {
// This function is to be used to know how many sealers are in the sealers list.
function sealerLength() public view returns (uint256) {
return sealers.length;
}
function sealerPosition( uint idx ) public view returns (address) {
require( idx <= sealers.length, "There are not that many sealers registered in the list." );
// This function is to be used to get the address from a specific position in the sealers list.
// Remember first position is 0.
// Last position is sealers.length-1.
function sealerPosition( uint256 idx ) public view returns (address) {
require( idx < sealers.length, "There are not that many sealers registered in the list." );
return sealers[idx];
}
function isSealer( address subject ) public view returns (bool) {
return ( sealerIdx(subject) > -1 );
}
function requireSealer( address subject ) public view {
require( isSealer(subject), "Not sealer" );
}
function _remove_entry( uint256 idx, list alist ) private returns (list) {
uint256 pos = idx;
// Move all items in the list (after idx) one step closer to the
// front of the list.
while ( ++pos < list.length )
alist[pos-1] = alist[pos];
// "pop" the end of the list, making the list shorter.
alist.length--;
return alist;
}
// This function sees if any proposals have overstayed their welcome and thus
// needs to be removed.
function _trimProposals() private {
uint i = adminproposals.length;
while ( i-- > 0 )
for ( uint256 i = sealerproposals.length-1; i>=0; i-- )
{
if ( adminproposals[i].votestart < block.number+30000 )
{
while ( i < adminproposals.length )
{
adminproposals[i] = adminproposals[i+1];
i--;
}
adminproposals.length--;
}
// If a proposal is more than 30K blocks old, then remove it from the list.
if ( sealerproposals[i].votestart + 30000 <= block.number )
sealerproposals = _remove_entry( i, sealerproposals );
}
}
function admin_promote( address victim, bool promotion ) public onlySealersTX {
// Janitor
_trimProposals();
bool isS = isSealer(victim);
function promotionIdx( address victim, bool promotion ) public view return (int256) {
uint256 idx = sealerproposals.length;
while ( idx-- > 0)
if ( sealerproposals[idx].victim == victim && sealerproposals[idx].promotion == promotion )
return idx;
return -1;
}
// You can call this for free and know if your promote call
// will get accepted. Save the network, call this first.
function mayVote( address voter, address victim, bool promotion ) public view returns (bool)
{
// Is caller a sealer?
if ( !isSealer(voter) )
return false;
// Is already Sealer and want to promote him?
if ( isS && promotion )
revert("You can't promote someone who is already a sealer.");
// Can't promote someone who is already a sealer.
if ( isSealer(victim) && promotion )
return false;
// Is not Sealer and want to demote him?
if ( !isS && !promotion )
revert("You can't demote someone who is not a sealer.");
// First see if we can find the index of an already running proposal for this victim,promotion tupple.
uint proppos = adminproposals.length;
while ( proppos>0 && adminproposals[proppos-1].victim != victim && adminproposals[proppos-1].promotion != promotion)
proppos--;
// If we found no matching proposals, we will add it, so we can now vote on it.
if (proppos == 0)
// Can't demote someone who is not a sealer.
if ( !isSealer(victim) && !promotion )
return false;
// See if the voter is already in the list of voters for this [victim,promotion] tupple
int256 proppos = promotionIdx( victim, promotion );
if ( proppos > -1 && findAddressInList( sealerproposals[proppos].voters, voter ) > -1 )
return false;
return true;
}
// Calling this function will vote for adding an additional or
// removing an existing member of "the inner circle".
// As per usual, this requires n/2+1 votes.
// The boolean must be true if you want to add a sealer
// and false if you want to remove one.
function promote( address victim, bool promotion ) public {
if ( ! mayVote(msg.sender, victim, promotion))
revert("That seems redundant.");
_trimProposals();
// Send notification of the vote
emit vote( msg.sender, victim, promotion );
// signed as opposed to unsigned (int)
int256 signedretval = promotionIdx( victim, promotion );
uint proppos;
if ( signedretval == -1 )
{
address[] memory sillycompiler;
proppos = adminproposals.push( Proposal(victim, block.number, promotion, sillycompiler ) );
// This is a new proposal, so we will add it, so we can vote on it.
address[] memory emptyAddressList;
proppos = sealerproposals.push( Proposal(victim, block.number, promotion, emptyAddressList ) );
}
// substract one, to make proppos actually reflect the position in the index.
proppos--;
else
proppos = uint256( signedretval & 0x7fffffffffffffffffffffffffffffff );
// Make things a bit more easy to read by using a shorter variable name
Proposal memory prop = adminproposals[proppos];
// Now look through the proposal and see if this sender already voted.
uint voterpos = prop.voters.length;
while ( voterpos>0 && prop.voters[voterpos-1] != msg.sender )
voterpos--;
// Return if sender already voted
if ( voterpos > 0 )
revert("You can not vote twice.");
// Send notification of the valid vote
emit vote( msg.sender, victim, promotion );
// Do we have enough votes to perform the operation?
Proposal memory prop = sealerproposals[proppos];
// Add our vote
prop.voters.push( msg.sender );
// Stop here if we do not have enough votes to perform the actual promotion/demotion
if ( prop.voters.length < sealers.length/2+1 )
return;
//
// Remove the proposal because the voting is complete.
sealerproposals = _remove_entry( proppos, sealerproposals );
// Is it a promotion or a demotion?
if ( prop.promotion )
{
if ( promotion )
// Add victim to sealer list
sealers.push( victim );
}
else
{
uint i = sealers.length;
// Delete all occurences of the victim from the sealer list (should be just a single occurence)
while ( i-- > 0 ) {
if (sealers[i] == victim )
{
for (uint j=i; j<sealers.length-1; j--)
sealers[j] = sealers[j+1];
sealers.length--;
}
}
}
// Remove the proposal, as the voting is complete.
for (i=proppos; i<adminproposals.length-1; i--)
adminproposals[i] = adminproposals[i+1];
adminproposals.length--;
// Remove victim from sealer list
sealers = _remove_entry( sealerIdx(victim), sealers );
// Send notification
emit adminChange( victim, promotion );
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment