forked from Qortal/qortal
Added some more useful tools/scripts, mostly for Linux-based curious node owners
This commit is contained in:
parent
4cc0e7845f
commit
6f2dd6c8d0
13
tools/peer-heights
Executable file
13
tools/peer-heights
Executable file
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Requires: 'qort' script in PATH, and 'jq' utility installed
|
||||
|
||||
set -e
|
||||
|
||||
# Any extra args passed to us are also passed to 'qort', just prior to '-p peers'
|
||||
qort $@ -p peers | \
|
||||
jq -r 'def lpad($len):
|
||||
tostring | ($len - length) as $l | (" " * $l)[:$l] + .;
|
||||
.[] |
|
||||
select(has("lastHeight")) |
|
||||
"\(.address | lpad(22)) (\(.version)), height \(.lastHeight), sig: \(.lastBlockSignature[0:8]), ts \(.lastBlockTimestamp / 1e3 | strftime("%Y-%m-%d %H:%M:%S"))"'
|
87
tools/qort
Executable file
87
tools/qort
Executable file
@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# default output post-processor
|
||||
postproc=cat
|
||||
|
||||
# Qortal defaults
|
||||
port=12391
|
||||
example_host=node10.qortal.org
|
||||
|
||||
# called-as name
|
||||
name="${0##*/}"
|
||||
|
||||
while [ -n "$*" ]; do
|
||||
case $1 in
|
||||
-p)
|
||||
shift
|
||||
postproc="json_pp -f json -t json -json_opt utf8,pretty"
|
||||
;;
|
||||
|
||||
[Dd][Ee][Ll][Ee][Tt][Ee])
|
||||
shift
|
||||
method="-X DELETE"
|
||||
;;
|
||||
|
||||
-l)
|
||||
shift
|
||||
src="--interface localhost"
|
||||
;;
|
||||
|
||||
-t)
|
||||
shift
|
||||
testnet=true
|
||||
;;
|
||||
|
||||
-j)
|
||||
shift
|
||||
content_type="Content-Type: application/json"
|
||||
;;
|
||||
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "${name}" = "qort" ]; then
|
||||
port=${testnet:+62391}
|
||||
port=${port:-12391}
|
||||
example_host=node10.qortal.org
|
||||
fi
|
||||
|
||||
if [ -z "$*" ]; then
|
||||
echo "usage: $name [-l] [-p] [-t] [DELETE] <url> [<post-data>]"
|
||||
echo "-l: use localhost as source address"
|
||||
echo "-p: pretty-print JSON output"
|
||||
echo "-t: use testnet port"
|
||||
echo "example (using localhost:${port}): $name -p blocks/last"
|
||||
echo "example: $name -p http://${example_host}:${port}/blocks/last"
|
||||
echo "example: BASE_URL=http://${example_host}:${port} $name -p blocks/last"
|
||||
echo "example: BASE_URL=${example_host} $name -p blocks/last"
|
||||
echo "example: $name -l DELETE peers/known"
|
||||
exit
|
||||
fi
|
||||
|
||||
url=$1
|
||||
shift
|
||||
|
||||
if [ "${url:0:4}" != "http" ]; then
|
||||
base_url=${BASE_URL-localhost:${port}}
|
||||
|
||||
if [ "${base_url:0:4}" != "http" ]; then
|
||||
base_url="http://${base_url}"
|
||||
fi
|
||||
|
||||
if [ -n "${base_url/#*:[0-9[0-9]*}" ]; then
|
||||
base_url="${base_url%%/}:${port}"
|
||||
fi
|
||||
|
||||
url="${base_url%%/}/${url#/}"
|
||||
fi
|
||||
|
||||
if [ "$#" != 0 ]; then
|
||||
data="--data"
|
||||
fi
|
||||
|
||||
curl --silent --insecure --connect-timeout 5 ${content_type:+--header} "${content_type}" ${method} ${src} --url ${url} ${data} "$@" | ${postproc}
|
||||
echo
|
353
tools/tx.pl
Executable file
353
tools/tx.pl
Executable file
@ -0,0 +1,353 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use JSON;
|
||||
use warnings;
|
||||
use strict;
|
||||
|
||||
use Getopt::Std;
|
||||
use File::Basename;
|
||||
|
||||
our %opt;
|
||||
getopts('dpst', \%opt);
|
||||
|
||||
my $proc = basename($0);
|
||||
|
||||
if (@ARGV < 1) {
|
||||
print STDERR "usage: $proc [-d] [-p] [-s] [-t] <tx-type> <privkey> <values> [<key-value pairs>]\n";
|
||||
print STDERR "-d: debug, -p: process (broadcast) transaction, -s: sign, -t: testnet\n";
|
||||
print STDERR "example: $proc PAYMENT P22kW91AJfDNBj32nVii292hhfo5AgvUYPz5W12ExsjE QxxQZiK7LZBjmpGjRz1FAZSx9MJDCoaHqz 0.1\n";
|
||||
print STDERR "example: $proc JOIN_GROUP X92h3hf9k20kBj32nVnoh3XT14o5AgvUYPz5W12ExsjE 3\n";
|
||||
print STDERR "example: BASE_URL=node10.qortal.org $proc JOIN_GROUP CB2DW91AJfd47432nVnoh3XT14o5AgvUYPz5W12ExsjE 3\n";
|
||||
print STDERR "example: $proc -p sign C4ifh827ffDNBj32nVnoh3XT14o5AgvUYPz5W12ExsjE 111jivxUwerRw...Fjtu\n";
|
||||
print STDERR "for help: $proc all\n";
|
||||
print STDERR "for help: $proc REGISTER_NAME\n";
|
||||
exit 2;
|
||||
}
|
||||
|
||||
our $BASE_URL = $ENV{BASE_URL} || $opt{t} ? 'http://localhost:62391' : 'http://localhost:12391';
|
||||
our $DEFAULT_FEE = 0.001;
|
||||
|
||||
our %TRANSACTION_TYPES = (
|
||||
payment => {
|
||||
url => 'payments/pay',
|
||||
required => [qw(recipient amount)],
|
||||
key_name => 'senderPublicKey',
|
||||
},
|
||||
# groups
|
||||
set_group => {
|
||||
url => 'groups/setdefault',
|
||||
required => [qw(defaultGroupId)],
|
||||
key_name => 'creatorPublicKey',
|
||||
},
|
||||
create_group => {
|
||||
url => 'groups/create',
|
||||
required => [qw(groupName description isOpen approvalThreshold)],
|
||||
key_name => 'creatorPublicKey',
|
||||
},
|
||||
update_group => {
|
||||
url => 'groups/update',
|
||||
required => [qw(groupId newOwner newDescription newIsOpen newApprovalThreshold)],
|
||||
key_name => 'ownerPublicKey',
|
||||
},
|
||||
join_group => {
|
||||
url => 'groups/join',
|
||||
required => [qw(groupId)],
|
||||
key_name => 'joinerPublicKey',
|
||||
},
|
||||
leave_group => {
|
||||
url => 'groups/leave',
|
||||
required => [qw(groupId)],
|
||||
key_name => 'leaverPublicKey',
|
||||
},
|
||||
group_invite => {
|
||||
url => 'groups/invite',
|
||||
required => [qw(groupId invitee)],
|
||||
key_name => 'adminPublicKey',
|
||||
},
|
||||
group_kick => {
|
||||
url => 'groups/kick',
|
||||
required => [qw(groupId member reason)],
|
||||
key_name => 'adminPublicKey',
|
||||
},
|
||||
add_group_admin => {
|
||||
url => 'groups/addadmin',
|
||||
required => [qw(groupId member)],
|
||||
key_name => 'ownerPublicKey',
|
||||
},
|
||||
group_approval => {
|
||||
url => 'groups/approval',
|
||||
required => [qw(pendingSignature approval)],
|
||||
key_name => 'adminPublicKey',
|
||||
},
|
||||
# assets
|
||||
issue_asset => {
|
||||
url => 'assets/issue',
|
||||
required => [qw(assetName description quantity isDivisible)],
|
||||
key_name => 'issuerPublicKey',
|
||||
},
|
||||
update_asset => {
|
||||
url => 'assets/update',
|
||||
required => [qw(assetId newOwner)],
|
||||
key_name => 'ownerPublicKey',
|
||||
},
|
||||
transfer_asset => {
|
||||
url => 'assets/transfer',
|
||||
required => [qw(recipient amount assetId)],
|
||||
key_name => 'senderPublicKey',
|
||||
},
|
||||
create_order => {
|
||||
url => 'assets/order',
|
||||
required => [qw(haveAssetId wantAssetId amount price)],
|
||||
key_name => 'creatorPublicKey',
|
||||
},
|
||||
# names
|
||||
register_name => {
|
||||
url => 'names/register',
|
||||
required => [qw(name data)],
|
||||
key_name => 'registrantPublicKey',
|
||||
},
|
||||
update_name => {
|
||||
url => 'names/update',
|
||||
required => [qw(newName newData)],
|
||||
key_name => 'ownerPublicKey',
|
||||
},
|
||||
# reward-shares
|
||||
reward_share => {
|
||||
url => 'addresses/rewardshare',
|
||||
required => [qw(recipient rewardSharePublicKey sharePercent)],
|
||||
key_name => 'minterPublicKey',
|
||||
},
|
||||
# arbitrary
|
||||
arbitrary => {
|
||||
url => 'arbitrary',
|
||||
required => [qw(service dataType data)],
|
||||
key_name => 'senderPublicKey',
|
||||
},
|
||||
# chat
|
||||
chat => {
|
||||
url => 'chat',
|
||||
required => [qw(data)],
|
||||
optional => [qw(recipient isText isEncrypted)],
|
||||
key_name => 'senderPublicKey',
|
||||
defaults => { isText => 'true' },
|
||||
pow_url => 'chat/compute',
|
||||
},
|
||||
# misc
|
||||
publicize => {
|
||||
url => 'addresses/publicize',
|
||||
required => [],
|
||||
key_name => 'senderPublicKey',
|
||||
pow_url => 'addresses/publicize/compute',
|
||||
},
|
||||
# Cross-chain trading
|
||||
build_trade => {
|
||||
url => 'crosschain/build',
|
||||
required => [qw(initialQortAmount finalQortAmount fundingQortAmount secretHash bitcoinAmount)],
|
||||
optional => [qw(tradeTimeout)],
|
||||
key_name => 'creatorPublicKey',
|
||||
defaults => { tradeTimeout => 10800 },
|
||||
},
|
||||
trade_recipient => {
|
||||
url => 'crosschain/tradeoffer/recipient',
|
||||
required => [qw(atAddress recipient)],
|
||||
key_name => 'creatorPublicKey',
|
||||
remove => [qw(timestamp reference fee)],
|
||||
},
|
||||
trade_secret => {
|
||||
url => 'crosschain/tradeoffer/secret',
|
||||
required => [qw(atAddress secret)],
|
||||
key_name => 'recipientPublicKey',
|
||||
remove => [qw(timestamp reference fee)],
|
||||
},
|
||||
# These are fake transaction types to provide utility functions:
|
||||
sign => {
|
||||
url => 'transactions/sign',
|
||||
required => [qw{transactionBytes}],
|
||||
},
|
||||
);
|
||||
|
||||
my $tx_type = lc(shift(@ARGV));
|
||||
|
||||
if ($tx_type eq 'all') {
|
||||
printf STDERR "Transaction types: %s\n", join(', ', sort { $a cmp $b } keys %TRANSACTION_TYPES);
|
||||
exit 2;
|
||||
}
|
||||
|
||||
my $tx_info = $TRANSACTION_TYPES{$tx_type};
|
||||
|
||||
if (!$tx_info) {
|
||||
printf STDERR "Transaction type '%s' unknown\n", uc($tx_type);
|
||||
exit 1;
|
||||
}
|
||||
|
||||
my @required = @{$tx_info->{required}};
|
||||
|
||||
if (@ARGV < @required + 1) {
|
||||
printf STDERR "usage: %s %s <privkey> %s", $proc, uc($tx_type), join(' ', map { "<$_>"} @required);
|
||||
printf STDERR " %s", join(' ', map { "[$_ <$_>]" } @{$tx_info->{optional}}) if exists $tx_info->{optional};
|
||||
print "\n";
|
||||
exit 2;
|
||||
}
|
||||
|
||||
my $priv_key = shift @ARGV;
|
||||
|
||||
my $account = account($priv_key);
|
||||
my $raw;
|
||||
|
||||
if ($tx_type ne 'sign') {
|
||||
my %extras;
|
||||
|
||||
foreach my $required_arg (@required) {
|
||||
$extras{$required_arg} = shift @ARGV;
|
||||
}
|
||||
|
||||
# For CHAT we use a random reference
|
||||
if ($tx_type eq 'chat') {
|
||||
$extras{reference} = api('utils/random?length=64');
|
||||
}
|
||||
|
||||
%extras = (%extras, %{$tx_info->{defaults}}) if exists $tx_info->{defaults};
|
||||
|
||||
%extras = (%extras, @ARGV);
|
||||
|
||||
$raw = build_raw($tx_type, $account, %extras);
|
||||
printf "Raw: %s\n", $raw if $opt{d} || (!$opt{s} && !$opt{p});
|
||||
|
||||
# Some transaction types require proof-of-work, e.g. CHAT
|
||||
if (exists $tx_info->{pow_url}) {
|
||||
$raw = api($tx_info->{pow_url}, $raw);
|
||||
printf "Raw with PoW: %s\n", $raw if $opt{d};
|
||||
}
|
||||
} else {
|
||||
$raw = shift @ARGV;
|
||||
$opt{s}++;
|
||||
}
|
||||
|
||||
if ($opt{s}) {
|
||||
my $signed = sign($account->{private}, $raw);
|
||||
printf "Signed: %s\n", $signed if $opt{d} || $tx_type eq 'sign';
|
||||
|
||||
if ($opt{p}) {
|
||||
my $processed = process($signed);
|
||||
printf "Processed: %s\n", $processed if $opt{d};
|
||||
}
|
||||
|
||||
my $hex = api('utils/frombase58', $signed);
|
||||
# sig is last 64 bytes / 128 chars
|
||||
my $sighex = substr($hex, -128);
|
||||
|
||||
my $sig58 = api('utils/tobase58/{hex}', '', '{hex}', $sighex);
|
||||
printf "Signature: %s\n", $sig58;
|
||||
}
|
||||
|
||||
sub account {
|
||||
my ($creator) = @_;
|
||||
|
||||
my $account = { private => $creator };
|
||||
$account->{public} = api('utils/publickey', $creator);
|
||||
$account->{address} = api('addresses/convert/{publickey}', '', '{publickey}', $account->{public});
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
sub build_raw {
|
||||
my ($type, $account, %extras) = @_;
|
||||
|
||||
my $tx_info = $TRANSACTION_TYPES{$type};
|
||||
die("unknown tx type: $type\n") unless defined $tx_info;
|
||||
|
||||
my $ref = exists $extras{reference} ? $extras{reference} : lastref($account->{address});
|
||||
|
||||
my %json = (
|
||||
timestamp => time * 1000,
|
||||
reference => $ref,
|
||||
fee => $DEFAULT_FEE,
|
||||
);
|
||||
|
||||
$json{$tx_info->{key_name}} = $account->{public} if exists $tx_info->{key_name};
|
||||
|
||||
foreach my $required (@{$tx_info->{required}}) {
|
||||
die("missing tx field: $required\n") unless exists $extras{$required};
|
||||
}
|
||||
|
||||
while (my ($key, $value) = each %extras) {
|
||||
$json{$key} = $value;
|
||||
}
|
||||
|
||||
if (exists $tx_info->{remove}) {
|
||||
foreach my $key (@{$tx_info->{remove}}) {
|
||||
delete $json{$key};
|
||||
}
|
||||
}
|
||||
|
||||
my $json = "{\n";
|
||||
while (my ($key, $value) = each %json) {
|
||||
if (ref($value) eq 'ARRAY') {
|
||||
$json .= "\t\"$key\": [],\n";
|
||||
} else {
|
||||
$json .= "\t\"$key\": \"$value\",\n";
|
||||
}
|
||||
}
|
||||
# remove final comma
|
||||
substr($json, -2, 1) = '';
|
||||
$json .= "}\n";
|
||||
|
||||
printf "%s:\n%s\n", $type, $json if $opt{d};
|
||||
|
||||
my $raw = api($tx_info->{url}, $json);
|
||||
return $raw;
|
||||
}
|
||||
|
||||
sub sign {
|
||||
my ($private, $raw) = @_;
|
||||
|
||||
my $json = <<" __JSON__";
|
||||
{
|
||||
"privateKey": "$private",
|
||||
"transactionBytes": "$raw"
|
||||
}
|
||||
__JSON__
|
||||
|
||||
return api('transactions/sign', $json);
|
||||
}
|
||||
|
||||
sub process {
|
||||
my ($signed) = @_;
|
||||
|
||||
return api('transactions/process', $signed);
|
||||
}
|
||||
|
||||
sub lastref {
|
||||
my ($address) = @_;
|
||||
|
||||
return api('addresses/lastreference/{address}', '', '{address}', $address)
|
||||
}
|
||||
|
||||
sub api {
|
||||
my ($endpoint, $postdata, @args) = @_;
|
||||
|
||||
my $url = $endpoint;
|
||||
my $method = 'GET';
|
||||
|
||||
for (my $i = 0; $i < @args; $i += 2) {
|
||||
my $placemarker = $args[$i];
|
||||
my $value = $args[$i + 1];
|
||||
|
||||
$url =~ s/$placemarker/$value/g;
|
||||
}
|
||||
|
||||
my $curl = "curl --silent --output - --url '$BASE_URL/$url'";
|
||||
if (defined $postdata && $postdata ne '') {
|
||||
$postdata =~ tr|\n| |s;
|
||||
$curl .= " --header 'Content-Type: application/json' --data-binary '$postdata'";
|
||||
$method = 'POST';
|
||||
}
|
||||
my $response = `$curl 2>/dev/null`;
|
||||
chomp $response;
|
||||
|
||||
if ($response eq '' || substr($response, 0, 6) eq '<html>' || $response =~ m/(^\{|,)"error":(\d+)[,}]/) {
|
||||
die("API call '$method $BASE_URL/$endpoint' failed:\n$response\n");
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
Loading…
Reference in New Issue
Block a user