Skip to content

Commit

Permalink
Merge bitcoin#1242: [RPC] Add coldstaking address support in importpr…
Browse files Browse the repository at this point in the history
…ivkey and importaddress

ce93872 [Trivial][RPC] Fix example line in importprivkey help (missing arg) (random-zebra)
96bdd3e [Tests] Add wallet_import_stakingaddress to test runner (random-zebra)
d1ebf2e [Tests] Add functional test for import staking address/key (random-zebra)
3bd5579 [RPC] Add coldstaking address support in importaddress (random-zebra)
144ec35 [RPC] Add coldstaking address support in importprivkey (random-zebra)

Pull request description:

  Still it remains to add proper support in `dumpwallet` and `importwallet`

ACKs for top commit:
  Fuzzbawls:
    ACK ce93872

Tree-SHA512: 604a19ca1942ef972ab05360823701021dcc9e12243859514a4196b36789ff06613541bbe7b5302409fb1056375186713af1a9a3ed01afb3806426bac3bed456
  • Loading branch information
furszy committed Jan 10, 2020
2 parents 310deb9 + ce93872 commit 2bfde4e
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 27 deletions.
1 change: 1 addition & 0 deletions src/rpc/client.cpp
Expand Up @@ -106,6 +106,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{"lockunspent", 0},
{"lockunspent", 1},
{"importprivkey", 2},
{"importprivkey", 3},
{"importaddress", 2},
{"verifychain", 0},
{"verifychain", 1},
Expand Down
55 changes: 31 additions & 24 deletions src/wallet/rpcdump.cpp
Expand Up @@ -75,16 +75,17 @@ std::string DecodeDumpString(const std::string& str)

UniValue importprivkey(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() < 1 || params.size() > 3)
if (fHelp || params.size() < 1 || params.size() > 4)
throw std::runtime_error(
"importprivkey \"pivxprivkey\" ( \"label\" rescan )\n"
"importprivkey \"pivxprivkey\" ( \"label\" rescan fStakingAddress )\n"
"\nAdds a private key (as returned by dumpprivkey) to your wallet.\n" +
HelpRequiringPassphrase() + "\n"

"\nArguments:\n"
"1. \"pivxprivkey\" (string, required) The private key (see dumpprivkey)\n"
"1. \"pivxprivkey\" (string, required) The private key (see dumpprivkey)\n"
"2. \"label\" (string, optional, default=\"\") An optional label\n"
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
"4. fStakingAddress (boolean, optional, default=false) Whether this key refers to a (cold) staking address\n"

"\nNote: This call can take minutes to complete if rescan is true.\n"

Expand All @@ -98,34 +99,31 @@ UniValue importprivkey(const UniValue& params, bool fHelp)
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false"));

LOCK2(cs_main, pwalletMain->cs_wallet);

EnsureWalletIsUnlocked();

std::string strSecret = params[0].get_str();
std::string strLabel = "";
if (params.size() > 1)
strLabel = params[1].get_str();

// Whether to perform rescan after import
bool fRescan = true;
if (params.size() > 2)
fRescan = params[2].get_bool();
const std::string strSecret = params[0].get_str();
const std::string strLabel = (params.size() > 1 ? params[1].get_str() : "");
const bool fRescan = (params.size() > 2 ? params[2].get_bool() : true);
const bool fStakingAddress = (params.size() > 3 ? params[3].get_bool() : false);

CBitcoinSecret vchSecret;
bool fGood = vchSecret.SetString(strSecret);

if (!fGood) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
if (!vchSecret.SetString(strSecret))
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");

CKey key = vchSecret.GetKey();
if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range");
if (!key.IsValid())
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range");

CPubKey pubkey = key.GetPubKey();
assert(key.VerifyPubKey(pubkey));
CKeyID vchAddress = pubkey.GetID();
{
LOCK2(cs_main, pwalletMain->cs_wallet);
EnsureWalletIsUnlocked();

pwalletMain->MarkDirty();
pwalletMain->SetAddressBook(vchAddress, strLabel, AddressBook::AddressBookPurpose::RECEIVE);
pwalletMain->SetAddressBook(vchAddress, strLabel, (
fStakingAddress ?
AddressBook::AddressBookPurpose::COLD_STAKING :
AddressBook::AddressBookPurpose::RECEIVE));

// Don't throw error in case a key is already there
if (pwalletMain->HaveKey(vchAddress))
Expand All @@ -140,7 +138,12 @@ UniValue importprivkey(const UniValue& params, bool fHelp)
pwalletMain->nTimeFirstKey = 1; // 0 would be considered 'no value'

if (fRescan) {
pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true);
CBlockIndex *pindex = chainActive.Genesis();
if (fStakingAddress && Params().NetworkID() != CBaseChainParams::REGTEST) {
// cold staking was activated after nBlockTimeProtocolV2. No need to scan the whole chain
pindex = chainActive[Params().BlockStartTimeProtocolV2()];
}
pwalletMain->ScanForWalletTransactions(pindex, true);
}
}

Expand Down Expand Up @@ -197,8 +200,12 @@ UniValue importaddress(const UniValue& params, bool fHelp)
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");

// add to address book or update label
if (address.IsValid())
pwalletMain->SetAddressBook(address.Get(), strLabel, AddressBook::AddressBookPurpose::RECEIVE);
if (address.IsValid()) {
pwalletMain->SetAddressBook(address.Get(), strLabel,
(address.IsStakingAddress() ?
AddressBook::AddressBookPurpose::COLD_STAKING :
AddressBook::AddressBookPurpose::RECEIVE));
}

// Don't throw error in case an address is already there
if (pwalletMain->HaveWatchOnly(script))
Expand Down
15 changes: 12 additions & 3 deletions test/functional/test_runner.py
Expand Up @@ -76,6 +76,7 @@
'zerocoin_wrapped_serials.py', # ~ 137 sec
'feature_uacomment.py', # ~ 130 sec
'mining_pos_fakestake.py', # ~ 123 sec
'wallet_import_stakingaddress.py', # ~ 123 sec

# vv Tests less than 2m vv
'p2p_disconnect_ban.py', # ~ 118 sec
Expand Down Expand Up @@ -261,9 +262,16 @@ def main():
if not args.keepcache:
shutil.rmtree("%s/test/cache" % config["environment"]["BUILDDIR"], ignore_errors=True)

run_tests(test_list, config["environment"]["SRCDIR"], config["environment"]["BUILDDIR"], config["environment"]["EXEEXT"], tmpdir, args.jobs, args.coverage, passon_args, args.combinedlogslen)
run_tests(test_list,
config["environment"]["SRCDIR"],
config["environment"]["BUILDDIR"],
config["environment"]["EXEEXT"],
tmpdir,
args.jobs, args.coverage,
passon_args, args.combinedlogslen,
args.keepcache)

def run_tests(test_list, src_dir, build_dir, exeext, tmpdir, jobs=1, enable_coverage=False, args=[], combined_logs_len=0):
def run_tests(test_list, src_dir, build_dir, exeext, tmpdir, jobs=1, enable_coverage=False, args=[], combined_logs_len=0, keep_cache=False):
# Warn if pivxd is already running (unix only)
try:
if subprocess.check_output(["pidof", "pivxd"]) is not None:
Expand Down Expand Up @@ -307,7 +315,8 @@ def pingTravis():
sys.stdout.flush()
threading.Timer(pingTime, pingTravis).start()

pingTravis()
if not keep_cache:
pingTravis()
try:
subprocess.check_output([tests_dir + 'create_cache.py'] + flags + ["--tmpdir=%s/cache" % tmpdir])
except subprocess.CalledProcessError as e:
Expand Down
89 changes: 89 additions & 0 deletions test/functional/wallet_import_stakingaddress.py
@@ -0,0 +1,89 @@
#!/usr/bin/env python3
# Copyright (c) 2020 The PIVX developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

'''
Tests importprivkey and importaddress with staking keys/addresses.
Node0 generates staking addresses and sends delegations to them.
Node1 imports and rescans. The test checks that cold utxos and staking balance is updated.
'''

from time import sleep

from test_framework.test_framework import PivxTestFramework
from test_framework.util import (
assert_equal,
DecimalAmt,
sync_blocks,
)

class ImportStakingTest(PivxTestFramework):

def set_test_params(self):
self.num_nodes = 2
self.extra_args = [['-staking=0']] * self.num_nodes
self.extra_args[0].append('-sporkkey=932HEevBSujW2ud7RfB1YF91AFygbBRQj3de3LyaCRqNzKKgWXi')

def log_title(self):
title = "*** Starting %s ***" % self.__class__.__name__
underline = "-" * len(title)
description = "Tests importprivkey and importaddress with staking keys/addresses."
self.log.info("\n\n%s\n%s\n%s\n", title, underline, description)

def run_test(self):
NUM_OF_DELEGATIONS = 4 # Create 2*NUM_OF_DELEGATIONS staking addresses
self.log_title()
self.log.info("Activating cold staking spork")
assert_equal("success", self.activate_spork(0, "SPORK_17_COLDSTAKING_ENFORCEMENT"))

# Create cold staking addresses and delegations
self.log.info("Creating new staking addresses and sending delegations")
staking_addresses = [self.nodes[0].getnewstakingaddress("label %d" % i)
for i in range(2 * NUM_OF_DELEGATIONS)]
delegations = []
for i, sa in enumerate(staking_addresses):
# delegate 10 PIV
delegations.append(self.nodes[0].delegatestake(sa, 10)['txid'])
# mine a block and check staking balance
self.nodes[0].generate(1)
assert_equal(self.nodes[0].getcoldstakingbalance(), DecimalAmt(10 * (i+1)))
sync_blocks(self.nodes)

# Export keys
self.log.info("Exporting keys and importing in node 1")
priv_keys = [self.nodes[0].dumpprivkey(x) for x in staking_addresses]

# Import keys of addresses 0-(NUM_OF_DELEGATIONS-1) (and rescan)
assert_equal(self.nodes[1].getcoldstakingbalance(), DecimalAmt(0))
for i, pk in enumerate(priv_keys[:NUM_OF_DELEGATIONS]):
self.nodes[1].importprivkey(pk, "label %d" % i, True, True)
val = self.nodes[1].validateaddress(staking_addresses[i])
assert_equal(val['ismine'], True)
assert_equal(val['isstaking'], True)
assert_equal(val['iswatchonly'], False)
assert_equal(self.nodes[1].getcoldstakingbalance(), DecimalAmt(10 * (i + 1)))
self.log.info("Balance of node 1 checks out")
coldutxos = [x['txid'] for x in self.nodes[1].listcoldutxos()]
assert_equal(len(coldutxos), NUM_OF_DELEGATIONS)
assert_equal(len([x for x in coldutxos if x in delegations]), NUM_OF_DELEGATIONS)
self.log.info("Delegation list of node 1 checks out")

# Import remaining addresses as watch-only (and rescan again)
self.log.info("Importing addresses (watch-only)")
for i, sa in enumerate(staking_addresses[NUM_OF_DELEGATIONS:]):
self.nodes[1].importaddress(sa, "label %d" % i, True)
# !TODO: add watch-only support in the core (balance and txes)
# Currently the only way to check the addressbook without the key here
# is to verify the account with validateaddress
val = self.nodes[1].validateaddress(sa)
assert_equal(val['ismine'], False)
assert_equal(val['isstaking'], True)
assert_equal(val['iswatchonly'], True)
assert_equal(self.nodes[1].getcoldstakingbalance(), DecimalAmt(10 * NUM_OF_DELEGATIONS))
self.log.info("Balance of node 1 checks out")



if __name__ == '__main__':
ImportStakingTest().main()

0 comments on commit 2bfde4e

Please sign in to comment.