Browse Source

Keeping HUSH on the down low

pull/32/head
Duke Leto 3 years ago
parent
commit
000ef4ac91
  1. 12
      doc/man/hush-cli.1
  2. 12
      doc/man/hush-tx.1
  3. 43
      doc/man/hushd.1
  4. 1
      src/Makefile.test.include
  5. 2
      src/init.cpp
  6. 17
      src/main.cpp
  7. 311
      src/rpc/blockchain.cpp
  8. 8
      src/rpc/net.cpp
  9. 14
      src/rpc/server.cpp
  10. 18
      src/rpc/server.h
  11. 440
      src/test/alert_tests.cpp
  12. 10
      src/util.cpp

12
doc/man/hush-cli.1

@ -3,7 +3,7 @@
.SH NAME
hush-cli \- manual page for hush-cli v3.6.1
.SH DESCRIPTION
Hush RPC client version v3.6.0\-e05a4e473\-dirty
Hush RPC client version v3.6.1\-cf5736569\-dirty
.PP
In order to ensure you are adequately protecting your privacy when using Hush,
please see <https://hush.is/security/>.
@ -75,12 +75,12 @@ Read extra arguments from standard input, one per line until EOF/Ctrl\-D
In order to ensure you are adequately protecting your privacy when using Hush,
please see <https://hush.is/security/>.
Copyright (C) 2009-2020 The Bitcoin Core Developers
Copyright (C) 2015-2020 The Zcash Developers
Copyright (C) 2015-2020 jl777 and SuperNET developers
Copyright (C) 2018-2020 Duke Leto and The Hush developers
Copyright (C) 2016-2021 Duke Leto and The Hush Developers
Copyright (C) 2016-2020 jl777 and SuperNET developers
Copyright (C) 2016-2018 The Zcash developers
Copyright (C) 2009-2014 The Bitcoin Core developers
This is experimental Free Software! Fuck Yeah!!
This is experimental Free Software! Fuck Yeah!!!!!
Distributed under the GPLv3 software license, see the accompanying file COPYING
or <https://www.gnu.org/licenses/gpl-3.0.en.html>.

12
doc/man/hush-tx.1

@ -3,7 +3,7 @@
.SH NAME
hush-tx \- manual page for hush-tx v3.6.1
.SH DESCRIPTION
hush\-tx utility version v3.6.0\-e05a4e473\-dirty
hush\-tx utility version v3.6.1\-cf5736569\-dirty
.SS "Usage:"
.TP
hush\-tx [options] <hex\-tx> [commands]
@ -88,12 +88,12 @@ Set register NAME to given JSON\-STRING
In order to ensure you are adequately protecting your privacy when using Hush,
please see <https://hush.is/security/>.
Copyright (C) 2009-2020 The Bitcoin Core Developers
Copyright (C) 2015-2020 The Zcash Developers
Copyright (C) 2015-2020 jl777 and SuperNET developers
Copyright (C) 2018-2020 Duke Leto and The Hush developers
Copyright (C) 2016-2021 Duke Leto and The Hush Developers
Copyright (C) 2016-2020 jl777 and SuperNET developers
Copyright (C) 2016-2018 The Zcash developers
Copyright (C) 2009-2014 The Bitcoin Core developers
This is experimental Free Software! Fuck Yeah!!
This is experimental Free Software! Fuck Yeah!!!!!
Distributed under the GPLv3 software license, see the accompanying file COPYING
or <https://www.gnu.org/licenses/gpl-3.0.en.html>.

43
doc/man/hushd.1

@ -3,7 +3,7 @@
.SH NAME
hushd \- manual page for hushd v3.6.1
.SH DESCRIPTION
Hush Daemon version v3.6.0\-e05a4e473\-dirty
Hush Daemon version v3.6.1\-cf5736569\-dirty
.PP
In order to ensure you are adequately protecting your privacy when using Hush,
please see <https://hush.is/security/>.
@ -41,7 +41,7 @@ Full node client name, default 'MagicBean'
.HP
\fB\-conf=\fR<file>
.IP
Specify configuration file (default: komodo.conf)
Specify configuration file (default: HUSH3.conf)
.HP
\fB\-daemon\fR
.IP
@ -226,7 +226,7 @@ Support filtering of blocks and transaction with Bloom filters (default:
.HP
\fB\-port=\fR<port>
.IP
Listen for connections on <port> (default: 7770 or testnet: 17770)
Listen for connections on <port> (default: 55555 or testnet: 55420)
.HP
\fB\-proxy=\fR<ip:port>
.IP
@ -256,13 +256,8 @@ Tor control port password (default: empty)
.HP
\fB\-tls=\fR<option>
.IP
Specify TLS usage (default: 1 => enabled and preferred, yet compatible);
other options are \fB\-tls\fR=\fI\,0\/\fR to disable TLS and \fB\-tls\fR=\fI\,only\/\fR to enforce it
.HP
\fB\-tlsfallbacknontls=\fR<0 or 1>
.IP
If a TLS connection fails, the next connection attempt of the same peer
(based on IP address) takes place without TLS (default: 1)
Specify TLS usage (default: 1 => enabled and required); Cannot be turned
off.
.HP
\fB\-tlsvalidate=\fR<0 or 1>
.IP
@ -285,15 +280,15 @@ Full path to a certificate
.IP
Full path to a trusted certificates directory
.HP
\fB\-whitebind=\fR<addr>
\fB\-allowbind=\fR<addr>
.IP
Bind to given address and whitelist peers connecting to it. Use
Bind to given address and allowlist peers connecting to it. Use
[host]:port notation for IPv6
.HP
\fB\-whitelist=\fR<netmask>
\fB\-allowlist=\fR<netmask>
.IP
Whitelist peers connecting from the given netmask or IP address. Can be
specified multiple times. Whitelisted peers cannot be DoS banned and
Allowlist peers connecting from the given netmask or IP address. Can be
specified multiple times. Allowlisted peers cannot be DoS banned and
their transactions are always relayed, even if they are already in the
mempool, useful e.g. for a gateway
.PP
@ -395,15 +390,15 @@ Make the wallet broadcast transactions (default: 1)
Execute command when a wallet transaction changes (%s in cmd is replaced
by TxID)
.HP
\fB\-whitelistaddress=\fR<Raddress>
\fB\-allowlistaddress=\fR<Raddress>
.IP
Enable the wallet filter for notary nodes and add one Raddress to the
whitelist of the wallet filter. If \fB\-whitelistaddress=\fR is used, then the
allowlist of the wallet filter. If \fB\-allowlistaddress=\fR is used, then the
wallet filter is automatically activated. Several Raddresses can be
defined using several \fB\-whitelistaddress=\fR (similar to \fB\-addnode\fR). The
defined using several \fB\-allowlistaddress=\fR (similar to \fB\-addnode\fR). The
wallet filter will filter the utxo to only ones coming from my own
Raddress (derived from pubkey) and each Raddress defined using
\fB\-whitelistaddress=\fR this option is mostly for Notary Nodes).
\fB\-allowlistaddress=\fR this option is mostly for Notary Nodes).
.HP
\fB\-zapwallettxes=\fR<mode>
.IP
@ -681,12 +676,12 @@ Enforce transaction\-rate limit, default 0
In order to ensure you are adequately protecting your privacy when using Hush,
please see <https://hush.is/security/>.
Copyright (C) 2009-2020 The Bitcoin Core Developers
Copyright (C) 2015-2020 The Zcash Developers
Copyright (C) 2015-2020 jl777 and SuperNET developers
Copyright (C) 2018-2020 Duke Leto and The Hush developers
Copyright (C) 2016-2021 Duke Leto and The Hush Developers
Copyright (C) 2016-2020 jl777 and SuperNET developers
Copyright (C) 2016-2018 The Zcash developers
Copyright (C) 2009-2014 The Bitcoin Core developers
This is experimental Free Software! Fuck Yeah!!
This is experimental Free Software! Fuck Yeah!!!!!
Distributed under the GPLv3 software license, see the accompanying file COPYING
or <https://www.gnu.org/licenses/gpl-3.0.en.html>.

1
src/Makefile.test.include

@ -52,7 +52,6 @@ BITCOIN_TESTS =\
test/arith_uint256_tests.cpp \
test/bignum.h \
test/addrman_tests.cpp \
test/alert_tests.cpp \
test/allocator_tests.cpp \
test/base32_tests.cpp \
test/base58_tests.cpp \

2
src/init.cpp

@ -375,7 +375,7 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-blocknotify=<cmd>", _("Execute command when the best block changes (%s in cmd is replaced by block hash)"));
strUsage += HelpMessageOpt("-checkblocks=<n>", strprintf(_("How many blocks to check at startup (default: %u, 0 = all)"), 288));
strUsage += HelpMessageOpt("-checklevel=<n>", strprintf(_("How thorough the block verification of -checkblocks is (0-4, default: %u)"), 3));
strUsage += HelpMessageOpt("-clientname=<SomeName>", _("Full node client name, default 'MagicBean'"));
strUsage += HelpMessageOpt("-clientname=<SomeName>", _("Full node client name, default 'GoldenSandtrout'"));
strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), "HUSH3.conf"));
if (mode == HMM_BITCOIND)
{

17
src/main.cpp

@ -6886,7 +6886,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
if (nVersion < minVersion)
{
// disconnect from peers older than this proto version
LogPrintf("Disconnecting peer=%d using obsolete version %i < %i\n", pfrom->id, nVersion, minVersion);
LogPrintf("Disconnecting peer=%d at %s using obsolete version %i < %i\n", pfrom->id, pfrom->addr.ToString().c_str(), nVersion, minVersion);
pfrom->PushMessage("reject", strCommand, REJECT_OBSOLETE, strprintf("Version must be %d or greater", minVersion));
pfrom->fDisconnect = true;
return false;
@ -6897,7 +6897,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
auto currentEpoch = CurrentEpoch(GetHeight(), params);
if (nVersion < params.vUpgrades[currentEpoch].nProtocolVersion)
{
LogPrintf("Disconnecting peer=%d using obsolete epoch version %i; disconnecting\n", pfrom->id, nVersion);
LogPrintf("Disconnecting peer=%d at %s using obsolete epoch version %i\n", pfrom->id, pfrom->addr.ToString().c_str(), nVersion);
pfrom->PushMessage("reject", strCommand, REJECT_OBSOLETE,
strprintf("Version must be %d or greater",
params.vUpgrades[currentEpoch].nProtocolVersion));
@ -7039,7 +7039,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
// 1. The version message has been received
// 2. Peer version is below the minimum version for the current epoch
else if (pfrom->nVersion < chainparams.GetConsensus().vUpgrades[CurrentEpoch(GetHeight(), chainparams.GetConsensus())].nProtocolVersion) {
LogPrintf("peer=%d using obsolete version %i vs %d; disconnecting\n", pfrom->id, pfrom->nVersion,(int32_t)chainparams.GetConsensus().vUpgrades[CurrentEpoch(GetHeight(), chainparams.GetConsensus())].nProtocolVersion);
LogPrintf("Disconnecting peer=%d at %s using obsolete version %i vs %d\n", pfrom->id, pfrom->addr.ToString().c_str(), pfrom->nVersion,(int32_t)chainparams.GetConsensus().vUpgrades[CurrentEpoch(GetHeight(), chainparams.GetConsensus())].nProtocolVersion);
pfrom->PushMessage("reject", strCommand, REJECT_OBSOLETE,
strprintf("Version must be %d or greater",
chainparams.GetConsensus().vUpgrades[
@ -7208,9 +7208,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
vector<CAddress> vAddr = addrman.GetAddr();
BOOST_FOREACH(const CAddress &addr, vAddr)
pfrom->PushAddress(addr);
}
else if (strCommand == "getnSPV")
{
} else if (strCommand == "getnSPV") {
if ( HUSH_NSPV == 0 )//&& HUSH_INSYNC != 0 )
{
std::vector<uint8_t> payload;
@ -7218,9 +7216,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
komodo_nSPVreq(pfrom,payload);
}
return(true);
}
else if (strCommand == "nSPV")
{
} else if (strCommand == "nSPV") {
if ( HUSH_NSPV_SUPERLITE )
{
std::vector<uint8_t> payload;
@ -7231,8 +7227,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
}
else if ( HUSH_NSPV_SUPERLITE )
return(true);
else if (strCommand == "inv")
{
else if (strCommand == "inv") {
vector<CInv> vInv;
vRecv >> vInv;
if (vInv.size() > MAX_INV_SZ)

311
src/rpc/blockchain.cpp

@ -1100,317 +1100,6 @@ UniValue paxprice(const UniValue& params, bool fHelp, const CPubKey& mypk)
}
return ret;
}
// fills pricedata with raw price, correlated and smoothed values for numblock
/*int32_t prices_extract(int64_t *pricedata,int32_t firstheight,int32_t numblocks,int32_t ind)
{
int32_t height,i,n,width,numpricefeeds = -1; uint64_t seed,ignore,rngval; uint32_t rawprices[1440*6],*ptr; int64_t *tmpbuf;
width = numblocks+PRICES_DAYWINDOW*2+PRICES_SMOOTHWIDTH; // need 2*PRICES_DAYWINDOW previous raw price points to calc PRICES_DAYWINDOW correlated points to calc, in turn, smoothed point
hush_heightpricebits(&seed,rawprices,firstheight + numblocks - 1);
if ( firstheight < width )
return(-1);
for (i=0; i<width; i++)
{
if ( (n= hush_heightpricebits(&ignore,rawprices,firstheight + numblocks - 1 - i)) < 0 ) // stores raw prices in backward order
return(-1);
if ( numpricefeeds < 0 )
numpricefeeds = n;
if ( n != numpricefeeds )
return(-2);
ptr = (uint32_t *)&pricedata[i*3];
ptr[0] = rawprices[ind];
ptr[1] = rawprices[0]; // timestamp
}
rngval = seed;
for (i=0; i<numblocks+PRICES_DAYWINDOW+PRICES_SMOOTHWIDTH; i++) // calculates +PRICES_DAYWINDOW more correlated values
{
rngval = (rngval*11109 + 13849);
ptr = (uint32_t *)&pricedata[i*3];
// takes previous PRICES_DAYWINDOW raw prices and calculates correlated price value
if ( (pricedata[i*3+1]= komodo_pricecorrelated(rngval,ind,(uint32_t *)&pricedata[i*3],6,0,PRICES_SMOOTHWIDTH)) < 0 ) // skip is 6 == sizeof(int64_t)/sizeof(int32_t)*3
return(-3);
}
tmpbuf = (int64_t *)calloc(sizeof(int64_t),2*PRICES_DAYWINDOW);
for (i=0; i<numblocks; i++)
// takes previous PRICES_DAYWINDOW correlated price values and calculates smoothed value
pricedata[i*3+2] = komodo_priceave(tmpbuf,&pricedata[i*3+1],3);
free(tmpbuf);
return(0);
}*/
UniValue prices(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if ( fHelp || params.size() != 1 )
throw runtime_error("prices maxsamples\n");
LOCK(cs_main);
UniValue ret(UniValue::VOBJ); uint64_t seed,rngval; int64_t *tmpbuf,smoothed,*correlated,checkprices[PRICES_MAXDATAPOINTS]; char name[64],*str; uint32_t rawprices[1440*6],*prices; uint32_t i,width,j,numpricefeeds=-1,n,numsamples,nextheight,offset,ht;
if ( ASSETCHAINS_CBOPRET == 0 )
throw JSONRPCError(RPC_INVALID_PARAMETER, "only -ac_cbopret chains have prices");
int32_t maxsamples = atoi(params[0].get_str().c_str());
if ( maxsamples < 1 )
maxsamples = 1;
nextheight = hush_nextheight();
UniValue a(UniValue::VARR);
if ( PRICES_DAYWINDOW < 7 )
throw JSONRPCError(RPC_INVALID_PARAMETER, "daywindow is too small");
width = maxsamples+2*PRICES_DAYWINDOW+PRICES_SMOOTHWIDTH;
numpricefeeds = hush_heightpricebits(&seed,rawprices,nextheight-1);
if ( numpricefeeds <= 0 )
throw JSONRPCError(RPC_INVALID_PARAMETER, "illegal numpricefeeds");
prices = (uint32_t *)calloc(sizeof(*prices),width*numpricefeeds);
correlated = (int64_t *)calloc(sizeof(*correlated),width);
i = 0;
for (ht=nextheight-1,i=0; i<width&&ht>2; i++,ht--)
{
if ( ht < 0 || ht > chainActive.Height() )
throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
else
{
if ( (n= hush_heightpricebits(0,rawprices,ht)) > 0 )
{
if ( n != numpricefeeds )
throw JSONRPCError(RPC_INVALID_PARAMETER, "numprices != first numprices");
else
{
for (j=0; j<numpricefeeds; j++)
prices[j*width + i] = rawprices[j];
}
} else throw JSONRPCError(RPC_INVALID_PARAMETER, "no komodo_rawprices found");
}
}
numsamples = i;
ret.push_back(Pair("firstheight", (int64_t)nextheight-1-i));
UniValue timestamps(UniValue::VARR);
for (i=0; i<maxsamples; i++)
timestamps.push_back((int64_t)prices[i]);
ret.push_back(Pair("timestamps",timestamps));
rngval = seed;
//for (i=0; i<PRICES_DAYWINDOW; i++)
// fprintf(stderr,"%.4f ",(double)prices[width+i]/10000);
//fprintf(stderr," maxsamples.%d\n",maxsamples);
for (j=1; j<numpricefeeds; j++)
{
UniValue item(UniValue::VOBJ),p(UniValue::VARR);
if ( (str= komodo_pricename(name,j)) != 0 )
{
item.push_back(Pair("name",str));
if ( numsamples >= width )
{
for (i=0; i<maxsamples+PRICES_DAYWINDOW+PRICES_SMOOTHWIDTH&&i<numsamples; i++)
{
offset = j*width + i;
rngval = (rngval*11109 + 13849);
if ( (correlated[i]= komodo_pricecorrelated(rngval,j,&prices[offset],1,0,PRICES_SMOOTHWIDTH)) < 0 )
throw JSONRPCError(RPC_INVALID_PARAMETER, "null correlated price");
{
if ( komodo_priceget(checkprices,j,nextheight-1-i,1) >= 0 )
{
if ( checkprices[1] != correlated[i] )
{
//fprintf(stderr,"ind.%d ht.%d %.8f != %.8f\n",j,nextheight-1-i,(double)checkprices[1]/COIN,(double)correlated[i]/COIN);
correlated[i] = checkprices[1];
}
}
}
}
tmpbuf = (int64_t *)calloc(sizeof(int64_t),2*PRICES_DAYWINDOW);
for (i=0; i<maxsamples&&i<numsamples; i++)
{
offset = j*width + i;
smoothed = komodo_priceave(tmpbuf,&correlated[i],1);
if ( komodo_priceget(checkprices,j,nextheight-1-i,1) >= 0 )
{
if ( checkprices[2] != smoothed )
{
fprintf(stderr,"ind.%d ht.%d %.8f != %.8f\n",j,nextheight-1-i,(double)checkprices[2]/COIN,(double)smoothed/COIN);
smoothed = checkprices[2];
}
}
UniValue parr(UniValue::VARR);
parr.push_back(ValueFromAmount((int64_t)prices[offset] * komodo_pricemult(j)));
parr.push_back(ValueFromAmount(correlated[i]));
parr.push_back(ValueFromAmount(smoothed));
// compare to alternate method
p.push_back(parr);
}
free(tmpbuf);
}
else
{
for (i=0; i<maxsamples&&i<numsamples; i++)
{
offset = j*width + i;
UniValue parr(UniValue::VARR);
parr.push_back(ValueFromAmount((int64_t)prices[offset] * komodo_pricemult(j)));
p.push_back(parr);
}
}
item.push_back(Pair("prices",p));
} else item.push_back(Pair("name","error"));
a.push_back(item);
}
ret.push_back(Pair("pricefeeds",a));
ret.push_back(Pair("result","success"));
ret.push_back(Pair("seed",(int64_t)seed));
ret.push_back(Pair("height",(int64_t)nextheight-1));
ret.push_back(Pair("maxsamples",(int64_t)maxsamples));
ret.push_back(Pair("width",(int64_t)width));
ret.push_back(Pair("daywindow",(int64_t)PRICES_DAYWINDOW));
ret.push_back(Pair("numpricefeeds",(int64_t)numpricefeeds));
free(prices);
free(correlated);
return ret;
}
// pricesbet rpc implementation
UniValue pricesbet(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (fHelp || params.size() != 3)
throw runtime_error("pricesbet amount leverage \"synthetic-expression\"\n"
"amount is in coins\n"
"leverage is integer non-zero value, positive for long, negative for short position\n"
"synthetic-expression example \"BTC_USD, 1\"\n");
LOCK(cs_main);
UniValue ret(UniValue::VOBJ);
if (ASSETCHAINS_CBOPRET == 0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "only -ac_cbopret chains have prices");
CAmount txfee = 10000;
CAmount amount = atof(params[0].get_str().c_str()) * COIN;
int16_t leverage = (int16_t)atoi(params[1].get_str().c_str());
if (leverage == 0)
throw runtime_error("invalid leverage\n");
std::string sexpr = params[2].get_str();
std::vector<std::string> vexpr;
SplitStr(sexpr, vexpr);
// debug print parsed strings:
std::cerr << "parsed synthetic: ";
for (auto s : vexpr)
std::cerr << s << " ";
std::cerr << std::endl;
return PricesBet(txfee, amount, leverage, vexpr);
}
// pricesaddfunding rpc implementation
UniValue pricesaddfunding(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (fHelp || params.size() != 2)
throw runtime_error("pricesaddfunding bettxid amount\n"
"where amount is in coins\n");
LOCK(cs_main);
UniValue ret(UniValue::VOBJ);
if (ASSETCHAINS_CBOPRET == 0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "only -ac_cbopret chains have prices");
CAmount txfee = 10000;
uint256 bettxid = Parseuint256(params[0].get_str().c_str());
if (bettxid.IsNull())
throw runtime_error("invalid bettxid\n");
CAmount amount = atof(params[1].get_str().c_str()) * COIN;
if (amount <= 0)
throw runtime_error("invalid amount\n");
return PricesAddFunding(txfee, bettxid, amount);
}
// rpc pricessetcostbasis implementation
UniValue pricessetcostbasis(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (fHelp || params.size() != 1)
throw runtime_error("pricessetcostbasis bettxid\n");
LOCK(cs_main);
UniValue ret(UniValue::VOBJ);
if (ASSETCHAINS_CBOPRET == 0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "only -ac_cbopret chains have prices");
uint256 bettxid = Parseuint256(params[0].get_str().c_str());
if (bettxid.IsNull())
throw runtime_error("invalid bettxid\n");
int64_t txfee = 10000;
return PricesSetcostbasis(txfee, bettxid);
}
// pricescashout rpc implementation
UniValue pricescashout(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (fHelp || params.size() != 1)
throw runtime_error("pricescashout bettxid\n");
LOCK(cs_main);
UniValue ret(UniValue::VOBJ);
if (ASSETCHAINS_CBOPRET == 0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "only -ac_cbopret chains have prices");
uint256 bettxid = Parseuint256(params[0].get_str().c_str());
if (bettxid.IsNull())
throw runtime_error("invalid bettxid\n");
int64_t txfee = 10000;
return PricesCashout(txfee, bettxid);
}
// pricesrekt rpc implementation
UniValue pricesrekt(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (fHelp || params.size() != 2)
throw runtime_error("pricesrekt bettxid height\n");
LOCK(cs_main);
UniValue ret(UniValue::VOBJ);
if (ASSETCHAINS_CBOPRET == 0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "only -ac_cbopret chains have prices");
uint256 bettxid = Parseuint256(params[0].get_str().c_str());
if (bettxid.IsNull())
throw runtime_error("invalid bettxid\n");
int32_t height = atoi(params[0].get_str().c_str());
int64_t txfee = 10000;
return PricesRekt(txfee, bettxid, height);
}
// pricesrekt rpc implementation
UniValue pricesgetorderbook(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (fHelp || params.size() != 0)
throw runtime_error("pricesgetorderbook\n");
LOCK(cs_main);
UniValue ret(UniValue::VOBJ);
if (ASSETCHAINS_CBOPRET == 0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "only -ac_cbopret chains have prices");
return PricesGetOrderbook();
}
// pricesrekt rpc implementation
UniValue pricesrefillfund(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (fHelp || params.size() != 1)
throw runtime_error("pricesrefillfund amount\n");
LOCK(cs_main);
UniValue ret(UniValue::VOBJ);
if (ASSETCHAINS_CBOPRET == 0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "only -ac_cbopret chains have prices");
CAmount amount = atof(params[0].get_str().c_str()) * COIN;
return PricesRefillFund(amount);
}
UniValue gettxout(const UniValue& params, bool fHelp, const CPubKey& mypk)
{

8
src/rpc/net.cpp

@ -101,7 +101,7 @@ UniValue getpeerinfo(const UniValue& params, bool fHelp, const CPubKey& mypk)
" \"pingtime\": n, (numeric) ping time\n"
" \"pingwait\": n, (numeric) ping wait\n"
" \"version\": v, (numeric) The peer version, such as 170002\n"
" \"subver\": \"/MagicBean:x.y.z[-v]/\", (string) The string version\n"
" \"subver\": \"/GoldenSandtrout:x.y.z[-v]/\", (string) The string version\n"
" \"inbound\": true|false, (boolean) Inbound (true) or Outbound (false)\n"
" \"startingheight\": n, (numeric) The starting height (block) of the peer\n"
" \"banscore\": n, (numeric) The ban score\n"
@ -470,7 +470,7 @@ UniValue getdeprecationinfo(const UniValue& params, bool fHelp, const CPubKey& m
"\nResult:\n"
"{\n"
" \"version\": xxxxx, (numeric) the server version\n"
" \"subversion\": \"/MagicBean:x.y.z[-v]/\", (string) the server subversion string\n"
" \"subversion\": \"/GoldenSandtrout:x.y.z[-v]/\", (string) the server subversion string\n"
" \"deprecationheight\": xxxxx, (numeric) the block height at which this version will deprecate and shut down\n"
"}\n"
"\nExamples:\n"
@ -481,7 +481,7 @@ UniValue getdeprecationinfo(const UniValue& params, bool fHelp, const CPubKey& m
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("version", CLIENT_VERSION));
obj.push_back(Pair("subversion",
FormatSubVersion(GetArg("-clientname","MagicBean"), CLIENT_VERSION, std::vector<string>())));
FormatSubVersion(GetArg("-clientname","GoldenSandtrout"), CLIENT_VERSION, std::vector<string>())));
obj.push_back(Pair("deprecationheight", DEPRECATION_HEIGHT));
return obj;
@ -496,7 +496,7 @@ UniValue getnetworkinfo(const UniValue& params, bool fHelp, const CPubKey& mypk)
"\nResult:\n"
"{\n"
" \"version\": xxxxx, (numeric) the server version\n"
" \"subversion\": \"/MagicBean:x.y.z[-v]/\", (string) the server subversion string\n"
" \"subversion\": \"/GoldenSandtrout:x.y.z[-v]/\", (string) the server subversion string\n"
" \"protocolversion\": xxxxx, (numeric) the protocol version\n"
" \"localservices\": \"xxxxxxxxxxxxxxxx\", (string) the services we offer to the network\n"
" \"timeoffset\": xxxxx, (numeric) the time offset (deprecated, always 0)\n"

14
src/rpc/server.cpp

@ -477,20 +477,6 @@ static const CRPCCommand vRPCCommands[] =
{ "oracles", "oraclessample", &oraclessample, true },
{ "oracles", "oraclessamples", &oraclessamples, true },
// Prices
{ "prices", "prices", &prices, true },
{ "prices", "pricesaddress", &pricesaddress, true },
{ "prices", "priceslist", &priceslist, true },
{ "prices", "mypriceslist", &mypriceslist, true },
{ "prices", "pricesinfo", &pricesinfo, true },
{ "prices", "pricesbet", &pricesbet, true },
{ "prices", "pricessetcostbasis", &pricessetcostbasis, true },
{ "prices", "pricescashout", &pricescashout, true },
{ "prices", "pricesrekt", &pricesrekt, true },
{ "prices", "pricesaddfunding", &pricesaddfunding, true },
{ "prices", "pricesgetorderbook", &pricesgetorderbook, true },
{ "prices", "pricesrefillfund", &pricesrefillfund, true },
// Pegs
{ "pegs", "pegsaddress", &pegsaddress, true },

18
src/rpc/server.h

@ -25,15 +25,12 @@
#include "amount.h"
#include "rpc/protocol.h"
#include "uint256.h"
#include <list>
#include <map>
#include <stdint.h>
#include <string>
#include <memory>
#include <boost/function.hpp>
#include <univalue.h>
#include <pubkey.h>
@ -275,10 +272,6 @@ extern UniValue oraclessubscribe(const UniValue& params, bool fHelp, const CPubK
extern UniValue oraclesdata(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue oraclessample(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue oraclessamples(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue pricesaddress(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue priceslist(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue mypriceslist(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue pricesinfo(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue pegsaddress(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue paymentsaddress(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue payments_release(const UniValue& params, bool fHelp, const CPubKey& mypk);
@ -516,15 +509,4 @@ extern UniValue paxprices(const UniValue& params, bool fHelp, const CPubKey& myp
extern UniValue paxdeposit(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue paxwithdraw(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue prices(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue pricesbet(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue pricessetcostbasis(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue pricescashout(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue pricesrekt(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue pricesaddfunding(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue pricesgetorderbook(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue pricesrefillfund(const UniValue& params, bool fHelp, const CPubKey& mypk);
#endif // HUSH_RPCSERVER_H

440
src/test/alert_tests.cpp

@ -1,440 +0,0 @@
// Copyright (c) 2013 The Bitcoin Core developers
// Copyright (c) 2016-2020 The Hush developers
// Distributed under the GPLv3 software license, see the accompanying
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
// Unit tests for alert system
#include "alert.h"
#include "chain.h"
#include "chainparams.h"
#include "clientversion.h"
#include "data/alertTests.raw.h"
#include "main.h"
#include "rpc/protocol.h"
#include "rpc/server.h"
#include "serialize.h"
#include "streams.h"
#include "util.h"
#include "utilstrencodings.h"
#include "test/test_bitcoin.h"
#include <fstream>
#include <boost/filesystem/operations.hpp>
#include <boost/foreach.hpp>
#include <boost/test/unit_test.hpp>
#include "key.h"
#include "alertkeys.h"
#include <iostream>
/*
* If the alert key pairs have changed, the test suite will fail as the
* test data is now invalid. To create valid test data, signed with a
* new alert private key, follow these steps:
*
* 1. Copy your private key into alertkeys.h. Don't commit this file!
* See sendalert.cpp for more info.
*
* 2. Set the GENERATE_ALERTS_FLAG to true.
*
* 3. Build and run:
* test_bitcoin -t Generate_Alert_Test_Data
*
* 4. Test data is saved in your current directory as alertTests.raw.NEW
* Copy this file to: src/test/data/alertTests.raw
*
* For debugging purposes, terminal output can be copied into:
* src/test/data/alertTests.raw.h
*
* 5. Clean up...
* - Set GENERATE_ALERTS_FLAG back to false.
* - Remove your private key from alertkeys.h
*
* 6. Build and verify the new test data:
* test_bitcoin -t Alert_tests
*
*/
#define GENERATE_ALERTS_FLAG false
#if GENERATE_ALERTS_FLAG
// NOTE:
// A function SignAndSave() was used by Bitcoin Core to create alert test data
// but it has not been made publicly available. So instead, we have adapted
// some publicly available code which achieves the intended result:
// https://gist.github.com/lukem512/9b272bd35e2cdefbf386
// Code to output a C-style array of values
template<typename T>
std::string HexStrArray(const T itbegin, const T itend, int lineLength)
{
std::string rv;
static const char hexmap[16] = { '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
rv.reserve((itend-itbegin)*3);
int i = 0;
for(T it = itbegin; it < itend; ++it)
{
unsigned char val = (unsigned char)(*it);
if(it != itbegin)
{
if (i % lineLength == 0)
rv.push_back('\n');
else
rv.push_back(' ');
}
rv.push_back('0');
rv.push_back('x');
rv.push_back(hexmap[val>>4]);
rv.push_back(hexmap[val&15]);
rv.push_back(',');
i++;
}
return rv;
}
template<typename T>
inline std::string HexStrArray(const T& vch, int lineLength)
{
return HexStrArray(vch.begin(), vch.end(), lineLength);
}
// Sign CAlert with alert private key
bool SignAlert(CAlert &alert)
{
// serialize alert data
CDataStream sMsg(SER_NETWORK, PROTOCOL_VERSION);
sMsg << *(CUnsignedAlert*)&alert;
alert.vchMsg = std::vector<unsigned char>(sMsg.begin(), sMsg.end());
// sign alert
std::vector<unsigned char> vchTmp(ParseHex(pszPrivKey));
CPrivKey vchPrivKey(vchTmp.begin(), vchTmp.end());
CKey key;
if (!key.SetPrivKey(vchPrivKey, false))
{
printf("key.SetPrivKey failed\n");
return false;
}
if (!key.Sign(Hash(alert.vchMsg.begin(), alert.vchMsg.end()), alert.vchSig))
{
printf("SignAlert() : key.Sign failed\n");
return false;
}
return true;
}
// Sign a CAlert and serialize it
bool SignAndSerialize(CAlert &alert, CDataStream &buffer)
{
// Sign
if(!SignAlert(alert))
{
printf("SignAndSerialize() : could not sign alert\n");
return false;
}
// ...and save!
buffer << alert;
return true;
}
void GenerateAlertTests()
{
CDataStream sBuffer(SER_DISK, CLIENT_VERSION);
CAlert alert;
alert.nRelayUntil = 60;
alert.nExpiration = 24 * 60 * 60;
alert.nID = 1;
alert.nCancel = 0; // cancels previous messages up to this ID number
alert.nMinVer = 0; // These versions are protocol versions
alert.nMaxVer = 999001;
alert.nPriority = 1;
alert.strComment = "Alert comment";
alert.strStatusBar = "Alert 1";
// Replace SignAndSave with SignAndSerialize
SignAndSerialize(alert, sBuffer);
// More tests go here ...
alert.setSubVer.insert(std::string("/MagicBean:0.1.0/"));
alert.strStatusBar = "Alert 1 for MagicBean 0.1.0";
SignAndSerialize(alert, sBuffer);
alert.setSubVer.insert(std::string("/MagicBean:0.2.0/"));
alert.strStatusBar = "Alert 1 for MagicBean 0.1.0, 0.2.0";
SignAndSerialize(alert, sBuffer);
alert.setSubVer.clear();
++alert.nID;
alert.nCancel = 1;
alert.nPriority = 100;
alert.strStatusBar = "Alert 2, cancels 1";
SignAndSerialize(alert, sBuffer);
alert.nExpiration += 60;
++alert.nID;
SignAndSerialize(alert, sBuffer);
++alert.nID;
alert.nPriority = 5000;
alert.strStatusBar = "Alert 3, disables RPC";
alert.strRPCError = "RPC disabled";
SignAndSerialize(alert, sBuffer);
++alert.nID;
alert.nPriority = 5000;
alert.strStatusBar = "Alert 4, re-enables RPC";
alert.strRPCError = "";
SignAndSerialize(alert, sBuffer);
++alert.nID;
alert.nMinVer = 11;
alert.nMaxVer = 22;
alert.nPriority = 100;
SignAndSerialize(alert, sBuffer);
++alert.nID;
alert.strStatusBar = "Alert 2 for MagicBean 0.1.0";
alert.setSubVer.insert(std::string("/MagicBean:0.1.0/"));
SignAndSerialize(alert, sBuffer);
++alert.nID;
alert.nMinVer = 0;
alert.nMaxVer = 999999;
alert.strStatusBar = "Evil Alert'; /bin/ls; echo '";
alert.setSubVer.clear();
bool b = SignAndSerialize(alert, sBuffer);
if (b) {
// Print the hex array, which will become the contents of alertTest.raw.h
std::vector<unsigned char> vch = std::vector<unsigned char>(sBuffer.begin(), sBuffer.end());
printf("%s\n", HexStrArray(vch, 8).c_str());
// Write the data to alertTests.raw.NEW, to be copied to src/test/data/alertTests.raw
std::ofstream outfile("alertTests.raw.NEW", std::ios::out | std::ios::binary);
outfile.write((const char*)&vch[0], vch.size());
outfile.close();
}
}
struct GenerateAlertTestsFixture : public TestingSetup {
GenerateAlertTestsFixture() {}
~GenerateAlertTestsFixture() {}
};
BOOST_FIXTURE_TEST_SUITE(Generate_Alert_Test_Data, GenerateAlertTestsFixture);
BOOST_AUTO_TEST_CASE(GenerateTheAlertTests)
{
GenerateAlertTests();
}
BOOST_AUTO_TEST_SUITE_END()
#else
struct ReadAlerts : public TestingSetup
{
ReadAlerts()
{
std::vector<unsigned char> vch(alert_tests::alertTests, alert_tests::alertTests + sizeof(alert_tests::alertTests));
CDataStream stream(vch, SER_DISK, CLIENT_VERSION);
try {
while (!stream.eof())
{
CAlert alert;
stream >> alert;
alerts.push_back(alert);
}
}
catch (const std::exception&) { }
}
~ReadAlerts() { }
static std::vector<std::string> read_lines(boost::filesystem::path filepath)
{
std::vector<std::string> result;
std::ifstream f(filepath.string().c_str());
std::string line;
while (std::getline(f,line))
result.push_back(line);
return result;
}
std::vector<CAlert> alerts;
};
BOOST_FIXTURE_TEST_SUITE(Alert_tests, ReadAlerts)
BOOST_AUTO_TEST_CASE(AlertApplies)
{
SetMockTime(11);
const std::vector<unsigned char>& alertKey = Params(CBaseChainParams::MAIN).AlertKey();
BOOST_FOREACH(const CAlert& alert, alerts)
{
BOOST_CHECK(alert.CheckSignature(alertKey));
}
BOOST_CHECK(alerts.size() >= 3);
// Matches:
BOOST_CHECK(alerts[0].AppliesTo(1, ""));
BOOST_CHECK(alerts[0].AppliesTo(999001, ""));
BOOST_CHECK(alerts[0].AppliesTo(1, "/MagicBean:11.11.11/"));
BOOST_CHECK(alerts[1].AppliesTo(1, "/MagicBean:0.1.0/"));
BOOST_CHECK(alerts[1].AppliesTo(999001, "/MagicBean:0.1.0/"));
BOOST_CHECK(alerts[2].AppliesTo(1, "/MagicBean:0.1.0/"));
BOOST_CHECK(alerts[2].AppliesTo(1, "/MagicBean:0.2.0/"));
// Don't match:
BOOST_CHECK(!alerts[0].AppliesTo(-1, ""));
BOOST_CHECK(!alerts[0].AppliesTo(999002, ""));
BOOST_CHECK(!alerts[1].AppliesTo(1, ""));
BOOST_CHECK(!alerts[1].AppliesTo(1, "MagicBean:0.1.0"));
BOOST_CHECK(!alerts[1].AppliesTo(1, "/MagicBean:0.1.0"));
BOOST_CHECK(!alerts[1].AppliesTo(1, "MagicBean:0.1.0/"));
BOOST_CHECK(!alerts[1].AppliesTo(-1, "/MagicBean:0.1.0/"));
BOOST_CHECK(!alerts[1].AppliesTo(999002, "/MagicBean:0.1.0/"));
BOOST_CHECK(!alerts[1].AppliesTo(1, "/MagicBean:0.2.0/"));
BOOST_CHECK(!alerts[2].AppliesTo(1, "/MagicBean:0.3.0/"));
SetMockTime(0);
}
BOOST_AUTO_TEST_CASE(AlertNotify)
{
SetMockTime(11);
const std::vector<unsigned char>& alertKey = Params(CBaseChainParams::MAIN).AlertKey();
boost::filesystem::path temp = GetTempPath() /
boost::filesystem::unique_path("alertnotify-%%%%.txt");
mapArgs["-alertnotify"] = std::string("echo %s >> ") + temp.string();
BOOST_FOREACH(CAlert alert, alerts)
alert.ProcessAlert(alertKey, false);
std::vector<std::string> r = read_lines(temp);
BOOST_CHECK_EQUAL(r.size(), 6u);
// Windows built-in echo semantics are different than posixy shells. Quotes and
// whitespace are printed literally.
#ifndef WIN32
BOOST_CHECK_EQUAL(r[0], "Alert 1");
BOOST_CHECK_EQUAL(r[1], "Alert 2, cancels 1");
BOOST_CHECK_EQUAL(r[2], "Alert 2, cancels 1");
BOOST_CHECK_EQUAL(r[3], "Alert 3, disables RPC");
BOOST_CHECK_EQUAL(r[4], "Alert 4, reenables RPC"); // dashes should be removed
BOOST_CHECK_EQUAL(r[5], "Evil Alert; /bin/ls; echo "); // single-quotes should be removed
#else
BOOST_CHECK_EQUAL(r[0], "'Alert 1' ");
BOOST_CHECK_EQUAL(r[1], "'Alert 2, cancels 1' ");
BOOST_CHECK_EQUAL(r[2], "'Alert 2, cancels 1' ");
BOOST_CHECK_EQUAL(r[3], "'Alert 3, disables RPC' ");
BOOST_CHECK_EQUAL(r[4], "'Alert 4, reenables RPC' "); // dashes should be removed
BOOST_CHECK_EQUAL(r[5], "'Evil Alert; /bin/ls; echo ' ");
#endif
boost::filesystem::remove(temp);
SetMockTime(0);
mapAlerts.clear();
}
BOOST_AUTO_TEST_CASE(AlertDisablesRPC)
{
SetMockTime(11);
const std::vector<unsigned char>& alertKey = Params(CBaseChainParams::MAIN).AlertKey();
// Command should work before alerts
BOOST_CHECK_EQUAL(GetWarnings("rpc"), "");
// First alert should disable RPC
alerts[5].ProcessAlert(alertKey, false);
BOOST_CHECK_EQUAL(alerts[5].strRPCError, "RPC disabled");
BOOST_CHECK_EQUAL(GetWarnings("rpc"), "RPC disabled");
// Second alert should re-enable RPC
alerts[6].ProcessAlert(alertKey, false);
BOOST_CHECK_EQUAL(alerts[6].strRPCError, "");
BOOST_CHECK_EQUAL(GetWarnings("rpc"), "");
SetMockTime(0);
mapAlerts.clear();
}
static bool falseFunc() { return false; }
BOOST_AUTO_TEST_CASE(PartitionAlert)
{
// Test PartitionCheck
CCriticalSection csDummy;
CBlockIndex indexDummy[400];
CChainParams& params = Params(CBaseChainParams::MAIN);
int64_t nPowTargetSpacing = params.GetConsensus().nPowTargetSpacing;
// Generate fake blockchain timestamps relative to
// an arbitrary time:
int64_t now = 1427379054;
SetMockTime(now);
for (int i = 0; i < 400; i++)
{
indexDummy[i].phashBlock = NULL;
if (i == 0) indexDummy[i].pprev = NULL;
else indexDummy[i].pprev = &indexDummy[i-1];
indexDummy[i].SetHeight(i);
indexDummy[i].nTime = now - (400-i)*nPowTargetSpacing;
// Other members don't matter, the partition check code doesn't
// use them
}
// Test 1: chain with blocks every nPowTargetSpacing seconds,
// as normal, no worries:
PartitionCheck(falseFunc, csDummy, &indexDummy[399], nPowTargetSpacing);
BOOST_CHECK(strMiscWarning.empty());
// Test 2: go 3.5 hours without a block, expect a warning:
now += 3*60*60+30*60;
SetMockTime(now);
PartitionCheck(falseFunc, csDummy, &indexDummy[399], nPowTargetSpacing);
BOOST_CHECK(!strMiscWarning.empty());
BOOST_TEST_MESSAGE(std::string("Got alert text: ")+strMiscWarning);
strMiscWarning = "";
// Test 3: test the "partition alerts only go off once per day"
// code:
now += 60*10;
SetMockTime(now);
PartitionCheck(falseFunc, csDummy, &indexDummy[399], nPowTargetSpacing);
BOOST_CHECK(strMiscWarning.empty());
// Test 4: get 2.5 times as many blocks as expected:
now += 60*60*24; // Pretend it is a day later
SetMockTime(now);
int64_t quickSpacing = nPowTargetSpacing*2/5;
for (int i = 0; i < 400; i++) // Tweak chain timestamps:
indexDummy[i].nTime = now - (400-i)*quickSpacing;
PartitionCheck(falseFunc, csDummy, &indexDummy[399], nPowTargetSpacing);
BOOST_CHECK(!strMiscWarning.empty());
BOOST_TEST_MESSAGE(std::string("Got alert text: ")+strMiscWarning);
strMiscWarning = "";
SetMockTime(0);
}
BOOST_AUTO_TEST_SUITE_END()
#endif

10
src/util.cpp

@ -986,12 +986,12 @@ std::string PrivacyInfo()
std::string LicenseInfo()
{
return "\n" +
FormatParagraph(strprintf(_("Copyright (C) 2009-%i The Bitcoin Core Developers"), COPYRIGHT_YEAR)) + "\n" +
FormatParagraph(strprintf(_("Copyright (C) 2015-%i The Zcash Developers"), COPYRIGHT_YEAR)) + "\n" +
FormatParagraph(strprintf(_("Copyright (C) 2015-%i jl777 and SuperNET developers"), COPYRIGHT_YEAR)) + "\n" +
FormatParagraph(strprintf(_("Copyright (C) 2018-%i Duke Leto and The Hush developers"), COPYRIGHT_YEAR)) + "\n" +
FormatParagraph(strprintf(_("Copyright (C) 2016-%i Duke Leto and The Hush Developers"), COPYRIGHT_YEAR)) + "\n" +
FormatParagraph(strprintf(_("Copyright (C) 2016-2020 jl777 and SuperNET developers"))) + "\n" +
FormatParagraph(strprintf(_("Copyright (C) 2016-2018 The Zcash developers"))) + "\n" +
FormatParagraph(strprintf(_("Copyright (C) 2009-2014 The Bitcoin Core developers"))) + "\n" +
"\n" +
FormatParagraph(_("This is experimental Free Software! Fuck Yeah!!")) + "\n" +
FormatParagraph(_("This is experimental Free Software! Fuck Yeah!!!!!")) + "\n" +
"\n" +
FormatParagraph(_("Distributed under the GPLv3 software license, see the accompanying file COPYING or <https://www.gnu.org/licenses/gpl-3.0.en.html>.")) + "\n" +
"\n";

Loading…
Cancel
Save