forked from Qortal/qortal
catbref
4 years ago
3 changed files with 453 additions and 0 deletions
@ -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"))"' |
@ -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 |
@ -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