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

Segwit light #82

Draft
wants to merge 14 commits into
base: Development
Choose a base branch
from
Draft
2 changes: 1 addition & 1 deletion divi/qa/rpc-tests/BadBlockTests.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def build_bad_sig_spend (self):
amountToSend = int ((Decimal (inp["amount"]) - Decimal ('0.1')) * COIN)
tx = CTransaction ()
tx.vout.append( CTxOut(amountToSend, scriptToSendTo ) )
tx.vin.append (CTxIn (COutPoint (txid=inp["txid"], n=inp["vout"])))
tx.vin.append (CTxIn (COutPoint (txid=inp["outputhash"], n=inp["vout"])))


unsigned = tx.serialize ().hex ()
Expand Down
2 changes: 1 addition & 1 deletion divi/qa/rpc-tests/BlocksOnlyHaveSingleCoinstake.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def build_coinstake_tx (self):
tx = CTransaction ()
tx.vout.append( CTxOut(0, CScript() ) )
tx.vout.append( CTxOut(amountToSend, scriptToSendTo ) )
tx.vin.append (CTxIn (COutPoint (txid=inp["txid"], n=inp["vout"])))
tx.vin.append (CTxIn (COutPoint (txid=inp["outputhash"], n=inp["vout"])))


unsigned = tx.serialize ().hex ()
Expand Down
2 changes: 1 addition & 1 deletion divi/qa/rpc-tests/TxInputsStandardness.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def generateOutput (self, addr, amount):
tx = self.node.getrawtransaction (txid, 1)
for i in range (len (tx["vout"])):
if tx["vout"][i]["scriptPubKey"]["addresses"] == [addr]:
return (txid, i)
return (tx["txid"], i)

raise AssertionError ("failed to find destination address")

Expand Down
137 changes: 137 additions & 0 deletions divi/qa/rpc-tests/around_segwit_light.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#!/usr/bin/env python3
# Copyright (c) 2020 The DIVI developers
# Distributed under the MIT/X11 software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

# Tests the policy changes in wallet and mempool around the
# segwit-light activation time that prevent spending of
# unconfirmed outputs.

from test_framework import BitcoinTestFramework
from authproxy import JSONRPCException
from util import *

from segwit_light import ACTIVATION_TIME

# Offset around the activation time where no restrictions are in place.
NO_RESTRICTIONS = 24 * 3_600

# Offset around the activation time where the wallet disallows
# spending unconfirmed change, but the mempool still accepts it.
WALLET_RESTRICTED = 10 * 3_600

# Offset around the activation time where wallet and mempool
# disallow spending unconfirmed outputs.
MEMPOOL_RESTRICTED = 3_600


class AroundSegwitLightTest (BitcoinTestFramework):

def setup_network (self, split=False):
args = ["-debug", "-spendzeroconfchange"]
self.nodes = start_nodes (1, self.options.tmpdir, extra_args=[args])
self.node = self.nodes[0]
self.is_network_split = False

def build_spending_chain (self, key, addr, initialValue):
"""
Spends the initialValue to addr, and then builds a follow-up
transaction that spends that output again back to addr with
a smaller value (for fees). The second transaction is built
using the raw-transactions API and returned as hex, not
submitted to the node already.
"""

txid = self.node.sendtoaddress (addr, initialValue)
data = self.node.getrawtransaction (txid, 1)
outputIndex = None
for i in range (len (data["vout"])):
if data["vout"][i]["value"] == initialValue:
outputIndex = i
break
assert outputIndex is not None

inputs = [{"txid": data[key], "vout": outputIndex}]
outputs = {addr: initialValue - 10}
tx = self.node.createrawtransaction (inputs, outputs)
signed = self.node.signrawtransaction (tx)
assert signed["complete"]

return signed["hex"]

def expect_unrestricted (self):
"""
Checks that spending of unconfirmed change is possible without
restrictions of wallet or mempool.
"""

balance = self.node.getbalance ()
addr = self.node.getnewaddress ()

self.node.sendtoaddress (addr, balance - 10)
self.node.sendtoaddress (addr, balance - 20)
self.node.setgenerate (True, 1)

def expect_wallet_restricted (self, key):
"""
Checks that the wallet forbids spending unconfirmed change,
while the mempool still allows it.
"""

balance = self.node.getbalance ()
addr = self.node.getnewaddress ()

self.node.sendtoaddress (addr, balance - 10)
assert_raises (JSONRPCException, self.node.sendtoaddress, addr, balance - 20)
self.node.setgenerate (True, 1)

balance = self.node.getbalance ()
tx = self.build_spending_chain (key, addr, balance - 10)
self.node.sendrawtransaction (tx)
self.node.setgenerate (True, 1)

def expect_mempool_restricted (self, key):
"""
Checks that the mempool does not allow spending unconfirmed
outputs (even if the transaction is built and submitted directly),
while blocks should still allow it.
"""

balance = self.node.getbalance ()
addr = self.node.getnewaddress ()

tx = self.build_spending_chain (key, addr, balance - 10)
assert_raises (JSONRPCException, self.node.sendrawtransaction, tx)
self.node.generateblock ({"extratx": [tx]})

def run_test (self):
self.node.setgenerate (True, 30)

# Before restrictions come into force, doing a normal
# spend of unconfirmed change through the wallet is fine.
set_node_times (self.nodes, ACTIVATION_TIME - NO_RESTRICTIONS)
self.expect_unrestricted ()

# Next the wallet doesn't allow those spends, but the mempool
# will (if done directly).
set_node_times (self.nodes, ACTIVATION_TIME - WALLET_RESTRICTED)
self.expect_wallet_restricted ("txid")

# Very close to the fork (on both sides), even the mempool won't
# allow spending unconfirmed change. If we include it directly in
# a block, it works.
set_node_times (self.nodes, ACTIVATION_TIME - MEMPOOL_RESTRICTED)
self.expect_mempool_restricted ("txid")
set_node_times (self.nodes, ACTIVATION_TIME + MEMPOOL_RESTRICTED)
self.node.setgenerate (True, 1)
self.expect_mempool_restricted ("baretxid")

# Finally, we should run into mempool-only or no restrictions
# at all if we go further into the future, away from the fork.
set_node_times (self.nodes, ACTIVATION_TIME + WALLET_RESTRICTED)
self.expect_wallet_restricted ("baretxid")
set_node_times (self.nodes, ACTIVATION_TIME + NO_RESTRICTIONS)
self.expect_unrestricted ()

if __name__ == '__main__':
AroundSegwitLightTest ().main ()
13 changes: 7 additions & 6 deletions divi/qa/rpc-tests/mnvaults.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# Copyright (c) 2020 The DIVI developers
# Copyright (c) 2020-2021 The DIVI developers
# Distributed under the MIT/X11 software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

Expand All @@ -26,7 +26,7 @@
class MnVaultsTest (MnTestFramework):

def __init__ (self):
super (MnVaultsTest, self).__init__ ()
super ().__init__ ()
self.base_args = ["-debug=masternode", "-debug=mocktime"]
self.cfg = None
self.number_of_nodes=7
Expand Down Expand Up @@ -94,6 +94,7 @@ def fund_vault (self):
amount = 100
txid = self.nodes[0].sendtoaddress (addr, amount)
raw = self.nodes[0].getrawtransaction (txid, 1)
outputId = raw["txid"]
vout = None
for i in range (len (raw["vout"])):
o = raw["vout"][i]
Expand All @@ -112,19 +113,19 @@ def fund_vault (self):
data = self.nodes[0].validateaddress (unvaultAddr)

tx = CTransaction ()
tx.vin.append (CTxIn (COutPoint (txid=txid, n=vout)))
tx.vin.append (CTxIn (COutPoint (txid=outputId, n=vout)))
tx.vout.append (CTxOut (amount * COIN, unhexlify (data["scriptPubKey"])))
unsigned = ToHex (tx)

validated = self.nodes[0].validateaddress (addr)
script = validated["scriptPubKey"]
prevtx = [{"txid": txid, "vout": vout, "scriptPubKey": script}]
prevtx = [{"txid": outputId, "vout": vout, "scriptPubKey": script}]
signed = self.nodes[0].signrawtransaction (unsigned, prevtx, [privkey],
"SINGLE|ANYONECANPAY")
assert_equal (signed["complete"], True)
self.unvaultTx = signed["hex"]

self.setup_masternode(2,1,"mn","copper",{"txhash":txid,"vout":vout})
self.setup_masternode(2,1,"mn","copper",{"txhash":outputId,"vout":vout})
self.cfg = self.setup[1].cfg
# FIXME: Use reward address from node 0.
self.cfg.rewardAddr = addr
Expand Down Expand Up @@ -231,7 +232,7 @@ def unvault (self):
data = self.nodes[0].validateaddress (changeAddr)

tx = FromHex (CTransaction (), self.unvaultTx)
tx.vin.append (CTxIn (COutPoint (txid=inp["txid"], n=inp["vout"])))
tx.vin.append (CTxIn (COutPoint (txid=inp["outputhash"], n=inp["vout"])))
tx.vout.append (CTxOut (change, unhexlify (data["scriptPubKey"])))
partial = ToHex (tx)

Expand Down
11 changes: 8 additions & 3 deletions divi/qa/rpc-tests/op_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ def build_op_meta_tx (self, utxos, payload, fee):
inp = None
for i in range (len (utxos)):
if utxos[i]["amount"] >= required:
inp = utxos[i]
inp = {
"txid": utxos[i]["outputhash"],
"vout": utxos[i]["vout"],
"amount": utxos[i]["amount"],
}
del utxos[i]
break
assert inp is not None, "found no suitable output"
Expand All @@ -59,8 +63,9 @@ def build_op_meta_tx (self, utxos, payload, fee):
tx = self.node.createrawtransaction ([inp], {changeAddr: change})
signed = self.node.signrawtransaction (tx)
assert_equal (signed["complete"], True)
txid = self.node.sendrawtransaction (signed["hex"])
inp["txid"] = txid
data = self.node.decoderawtransaction (signed["hex"])
self.node.sendrawtransaction (signed["hex"])
inp["txid"] = data["txid"]
inp["vout"] = 0
inp["amount"] = change

Expand Down
2 changes: 1 addition & 1 deletion divi/qa/rpc-tests/rawtransactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def find_output (self, node, value):

for u in node.listunspent ():
if u["amount"] == value:
return {"txid": u["txid"], "vout": u["vout"]}
return {"txid": u["outputhash"], "vout": u["vout"]}

raise AssertionError ("no output with value %s found" % str (value))

Expand Down
116 changes: 116 additions & 0 deletions divi/qa/rpc-tests/segwit_light.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/usr/bin/env python3
# Copyright (c) 2020 The DIVI developers
# Distributed under the MIT/X11 software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

# Tests the policy changes in wallet and mempool around the
# segwit-light activation time that prevent spending of
# unconfirmed outputs.

from test_framework import BitcoinTestFramework
from util import *

from PowToPosTransition import createPoSStacks, generatePoSBlocks

ACTIVATION_TIME = 2_100_000_000


class SegwitLightTest (BitcoinTestFramework):

def setup_network (self, split=False):
args = ["-debug", "-spendzeroconfchange"]
self.nodes = start_nodes (2, self.options.tmpdir, extra_args=[args]*2)
connect_nodes (self.nodes[0], 1)
self.is_network_split = False

def run_test (self):
# Activate the fork and PoS. We go beyond the fork to ensure
# the mempool/wallet limitations are lifted already.
set_node_times (self.nodes, ACTIVATION_TIME + 3_600 * 24 * 7)
reconnect_all (self.nodes)
self.nodes[0].setgenerate (True, 1)
sync_blocks (self.nodes)
createPoSStacks (self.nodes[:1], self.nodes)
generatePoSBlocks (self.nodes, 0, 100)
blk = self.nodes[1].getblockheader (self.nodes[1].getbestblockhash ())
assert_greater_than (blk["time"], ACTIVATION_TIME)

# Send some normal transactions from the wallet (but in a chain).
self.nodes[0].sendtoaddress (self.nodes[1].getnewaddress (), 1_000)
generatePoSBlocks (self.nodes, 0, 1)
assert_equal (self.nodes[1].getbalance (), 1_000)
addr = self.nodes[0].getnewaddress ()
id1 = self.nodes[1].sendtoaddress (addr, 900)
id2 = self.nodes[1].sendtoaddress (addr, 90)
id3 = self.nodes[1].sendtoaddress (addr, 9)
assert_equal (set (self.nodes[1].getrawmempool ()), set ([id1, id2, id3]))
sync_mempools (self.nodes)
generatePoSBlocks (self.nodes, 0, 1)
assert_equal (self.nodes[1].getrawmempool (), [])
assert_greater_than (1, self.nodes[1].getbalance ())

# Build a transaction on top of an unconfirmed one, that we will malleate.
# The prepared transaction should still be valid. For malleating, we use
# funds on a 1-of-2 multisig address, and then change which wallet
# is signing.
keys = [
n.validateaddress (n.getnewaddress ())["pubkey"]
for n in self.nodes
]
multisig = self.nodes[0].addmultisigaddress (1, keys)
assert_equal (self.nodes[1].addmultisigaddress (1, keys), multisig)
txid0 = self.nodes[0].sendtoaddress (multisig, 1_000)
data0 = self.nodes[0].getrawtransaction (txid0, 1)
btxid = data0["baretxid"]
outputIndex = None
for i in range (len (data0["vout"])):
if data0["vout"][i]["scriptPubKey"]["addresses"] == [multisig]:
assert outputIndex is None
outputIndex = i
assert outputIndex is not None
generatePoSBlocks (self.nodes, 0, 1)
out = self.nodes[0].gettxout (btxid, outputIndex)
assert_equal (out["confirmations"], 1)
assert_equal (out["value"], 1_000)
assert_equal (out["scriptPubKey"]["addresses"], [multisig])

inputs = [{"txid": btxid, "vout": outputIndex}]
tempAddr = self.nodes[0].getnewaddress ("temp")
outputs = {tempAddr: 999}
unsigned1 = self.nodes[0].createrawtransaction (inputs, outputs)
signed1 = self.nodes[0].signrawtransaction (unsigned1)
assert_equal (signed1["complete"], True)
signed1 = signed1["hex"]
data1 = self.nodes[0].decoderawtransaction (signed1)

prevtx = [
{
"txid": data1["baretxid"],
"vout": 0,
"scriptPubKey": self.nodes[0].validateaddress (tempAddr)["scriptPubKey"],
}
]
inputs = [{"txid": data1["baretxid"], "vout": 0}]
finalAddr = self.nodes[1].getnewaddress ("final")
outputs = {finalAddr: 998}
unsigned2 = self.nodes[0].createrawtransaction (inputs, outputs)
signed2 = self.nodes[0].signrawtransaction (unsigned2, prevtx)
assert_equal (signed2["complete"], True)
signed2 = signed2["hex"]
data2 = self.nodes[0].decoderawtransaction (signed2)

signed1p = self.nodes[1].signrawtransaction (unsigned1)
assert_equal (signed1p["complete"], True)
signed1p = signed1p["hex"]
data1p = self.nodes[0].decoderawtransaction (signed1p)
assert_equal (data1["baretxid"], data1p["baretxid"])
assert data1["txid"] != data1p["txid"]

self.nodes[0].sendrawtransaction (signed1p)
self.nodes[0].sendrawtransaction (signed2)
generatePoSBlocks (self.nodes, 0, 1)
sync_blocks (self.nodes)
assert_equal (self.nodes[1].getbalance ("final"), 998)

if __name__ == '__main__':
SegwitLightTest ().main ()
Loading