Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exclude spent outputs from remote staking balance #1000

Merged
merged 6 commits into from
Apr 17, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions src/esperanza/walletextension.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,16 @@ CAmount WalletExtension::GetRemoteStakingBalance() const {
CAmount balance = 0;

for (const auto &it : m_enclosing_wallet.mapWallet) {
const CWalletTx *const tx = &it.second;
const CWalletTx &tx = it.second;
const uint256 &tx_hash = tx.GetHash();

for (const auto &txout : tx->tx->vout) {
if (::IsStakedRemotely(m_enclosing_wallet, txout.scriptPubKey)) {
balance += txout.nValue;
for (size_t i = 0; i < tx.tx->vout.size(); ++i) {
const CTxOut &tx_out = tx.tx->vout[i];
if (m_enclosing_wallet.IsSpent(tx_hash, i)) {
continue;
}
if (::IsStakedRemotely(m_enclosing_wallet, tx_out.scriptPubKey)) {
balance += tx_out.nValue;
}
}
}
Expand Down
117 changes: 103 additions & 14 deletions test/functional/feature_remote_staking.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,38 @@
# Copyright (c) 2019 The Unit-e Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
from test_framework.mininode import sha256
from test_framework.regtest_mnemonics import regtest_mnemonics
from test_framework.script import CScript, OP_2, hash160
from test_framework.test_framework import UnitETestFramework, STAKE_SPLIT_THRESHOLD
from test_framework.util import assert_equal, assert_greater_than, bytes_to_hex_str, hex_str_to_bytes, wait_until
from test_framework.blocktools import (
create_block,
create_coinbase,
get_tip_snapshot_meta,
sign_coinbase,
)
from test_framework.mininode import (
msg_witness_block,
network_thread_start,
P2PInterface,
sha256,
)
from test_framework.regtest_mnemonics import (
regtest_mnemonics,
dsaveliev marked this conversation as resolved.
Show resolved Hide resolved
)
from test_framework.script import (
CScript,
OP_2,
hash160,
)
from test_framework.test_framework import (
UnitETestFramework,
PROPOSER_REWARD,
STAKE_SPLIT_THRESHOLD,
)
from test_framework.util import (
assert_equal,
assert_greater_than,
bytes_to_hex_str,
hex_str_to_bytes,
wait_until,
)


def stake_p2wsh(node, staking_node, amount):
Expand All @@ -18,23 +45,49 @@ def stake_p2wsh(node, staking_node, amount):
staking_node: the node which will be able to stake nodes
amount: the amount to send
"""
multisig = node.addmultisigaddress(2, [node.getnewaddress(), node.getnewaddress()])
multisig = node.addmultisigaddress(
2, [node.getnewaddress(), node.getnewaddress()])
bare = CScript(hex_str_to_bytes(multisig['redeemScript']))
spending_script_hash = sha256(bare)

addr_info = staking_node.validateaddress(staking_node.getnewaddress('', 'legacy'))
addr_info = staking_node.validateaddress(
staking_node.getnewaddress('', 'legacy'))
staking_key_hash = hash160(hex_str_to_bytes(addr_info['pubkey']))

rs_p2wsh = CScript([OP_2, staking_key_hash, spending_script_hash])
outputs = [{'address': 'script', 'amount': amount, 'script': bytes_to_hex_str(rs_p2wsh)}]
node.sendtypeto('unite', 'unite', outputs)
outputs = [{'address': 'script', 'amount': amount,
'script': bytes_to_hex_str(rs_p2wsh)}]
return node.sendtypeto('unite', 'unite', outputs)


def build_block_with_remote_stake(node):
height = node.getblockcount()
snapshot_meta = get_tip_snapshot_meta(node)
stakes = node.liststakeablecoins()

coin = stakes['stakeable_coins'][0]['coin']
pubkey = hex_str_to_bytes(coin['script_pub_key']['hex'])
dsaveliev marked this conversation as resolved.
Show resolved Hide resolved
stake = {
'txid': coin['out_point']['txid'],
'vout': coin['out_point']['n'],
'amount': coin['amount'],
}

tip = int(node.getbestblockhash(), 16)
block_time = node.getblock(
node.getbestblockhash())['time'] + 1
coinbase = sign_coinbase(
node, create_coinbase(
height, stake, snapshot_meta.hash, raw_script_pubkey=pubkey))

return create_block(tip, coinbase, block_time)


class RemoteStakingTest(UnitETestFramework):
def set_test_params(self):
self.num_nodes = 2
self.setup_clean_chain = True
self.extra_args=[
self.extra_args = [
[],
['-minimumchainwork=0', '-maxtipage=1000000000']
]
Expand All @@ -43,8 +96,13 @@ def run_test(self):
alice, bob = self.nodes
alice.importmasterkey(regtest_mnemonics[0]['mnemonics'])

bob.add_p2p_connection(P2PInterface())
network_thread_start()
bob.p2p.wait_for_verack()

alice.generate(1)
assert_equal(len(alice.listunspent()), regtest_mnemonics[0]['balance'] / STAKE_SPLIT_THRESHOLD)
assert_equal(len(alice.listunspent()),
regtest_mnemonics[0]['balance'] / STAKE_SPLIT_THRESHOLD)

alices_addr = alice.getnewaddress()

Expand All @@ -60,21 +118,52 @@ def run_test(self):
assert_equal(ps['wallets'][0]['stakeable_balance'], 0)

# Stake the funds
result = alice.stakeat(recipient)
stake_p2wsh(alice, staking_node=bob, amount=1)
tx1_hash = alice.stakeat(recipient)
tx2_hash = stake_p2wsh(alice, staking_node=bob, amount=1)
alice.generatetoaddress(1, alices_addr)
self.sync_all()

# Estimate Alice balance
# Fee is the sum of two transactions - p2wpkh and p2wsh
tx1_fee = alice.gettransaction(tx1_hash)['fee']
tx2_fee = alice.gettransaction(tx2_hash)['fee']
alice_balance = regtest_mnemonics[0]['balance'] + tx1_fee + tx2_fee

wi = alice.getwalletinfo()
assert_equal(wi['remote_staking_balance'], 2)
assert_equal(wi['balance'], alice_balance)

def bob_is_staking_the_new_coin():
ps = bob.proposerstatus()
return ps['wallets'][0]['stakeable_balance'] == 2
wait_until(bob_is_staking_the_new_coin, timeout=10)

# Bob generates a new block with remote stake of Alice
block = build_block_with_remote_stake(bob)
bob.p2p.send_and_ping(msg_witness_block(block))
self.sync_all()

# Reward from the Bob's block comes to remote staking balance of Alice
# But stake output from the Bob's block must be ignored
dsaveliev marked this conversation as resolved.
Show resolved Hide resolved
# Actual spendable balance shouldn't change
dsaveliev marked this conversation as resolved.
Show resolved Hide resolved
wi = alice.getwalletinfo()
assert_equal(wi['remote_staking_balance'], 2 + PROPOSER_REWARD)
assert_equal(wi['balance'], alice_balance)

# Change outputs for both staked coins, and the balance staked remotely
assert_equal(len(alice.listunspent()), 2 + (regtest_mnemonics[0]['balance'] // STAKE_SPLIT_THRESHOLD))
assert_equal(len(alice.listunspent()), 2 +
(regtest_mnemonics[0]['balance'] // STAKE_SPLIT_THRESHOLD))

# Spend coins, given to remote stake
dsaveliev marked this conversation as resolved.
Show resolved Hide resolved
inputs = []
for coin in bob.liststakeablecoins()['stakeable_coins']:
out_point = coin['coin']['out_point']
inputs.append({'tx': out_point['txid'], 'n': out_point['n']})
alice.sendtypeto('', '', [{'address': alices_addr, 'amount': 1.9}], '', '', False,
{'changeaddress': alices_addr, 'inputs': inputs})

wi = alice.getwalletinfo()
assert_equal(wi['remote_staking_balance'], PROPOSER_REWARD)


if __name__ == '__main__':
Expand Down
4 changes: 3 additions & 1 deletion test/functional/test_framework/blocktools.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def serialize_script_num(value):
# If pubkey is passed in, the coinbase outputs will be P2PK outputs;
# otherwise anyone-can-spend outputs. The first output is the reward,
# which is not spendable for COINBASE_MATURITY blocks.
def create_coinbase(height, stake, snapshot_hash, pubkey = None, n_pieces = 1):
def create_coinbase(height, stake, snapshot_hash, pubkey = None, raw_script_pubkey = None, n_pieces = 1):
dsaveliev marked this conversation as resolved.
Show resolved Hide resolved
assert n_pieces > 0
stake_in = COutPoint(int(stake['txid'], 16), stake['vout'])
coinbase = CTransaction()
Expand All @@ -81,6 +81,8 @@ def create_coinbase(height, stake, snapshot_hash, pubkey = None, n_pieces = 1):
output_script = None
if (pubkey != None):
output_script = CScript([pubkey, OP_CHECKSIG])
elif (raw_script_pubkey != None):
dsaveliev marked this conversation as resolved.
Show resolved Hide resolved
output_script = CScript(raw_script_pubkey)
else:
output_script = CScript([OP_TRUE])

Expand Down