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

Enforce coinbase maturity on bitcoin peg-ins #538

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
16 changes: 12 additions & 4 deletions src/pegins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ static bool CheckPeginTx(const std::vector<unsigned char>& tx_data, T& pegtx, co
}

template<typename T>
static bool GetBlockAndTxFromMerkleBlock(uint256& block_hash, uint256& tx_hash, T& merkle_block, const std::vector<unsigned char>& merkle_block_raw)
static bool GetBlockAndTxFromMerkleBlock(uint256& block_hash, uint256& tx_hash, unsigned int& tx_index, T& merkle_block, const std::vector<unsigned char>& merkle_block_raw)
{
try {
std::vector<uint256> tx_hashes;
Expand All @@ -205,6 +205,7 @@ static bool GetBlockAndTxFromMerkleBlock(uint256& block_hash, uint256& tx_hash,
return false;
}
tx_hash = tx_hashes[0];
tx_index = tx_indices[0];
} catch (std::exception& e) {
// Invalid encoding of merkle block
return false;
Expand Down Expand Up @@ -295,10 +296,11 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p
uint256 block_hash;
uint256 tx_hash;
int num_txs;
unsigned int tx_index = 0;
// Get txout proof
if (Params().GetConsensus().ParentChainHasPow()) {
Sidechain::Bitcoin::CMerkleBlock merkle_block_pow;
if (!GetBlockAndTxFromMerkleBlock(block_hash, tx_hash, merkle_block_pow, stack[5])) {
if (!GetBlockAndTxFromMerkleBlock(block_hash, tx_hash, tx_index, merkle_block_pow, stack[5])) {
err_msg = "Could not extract block and tx from merkleblock.";
return false;
}
Expand All @@ -316,7 +318,7 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p
num_txs = merkle_block_pow.txn.GetNumTransactions();
} else {
CMerkleBlock merkle_block;
if (!GetBlockAndTxFromMerkleBlock(block_hash, tx_hash, merkle_block, stack[5])) {
if (!GetBlockAndTxFromMerkleBlock(block_hash, tx_hash, tx_index, merkle_block, stack[5])) {
err_msg = "Could not extract block and tx from merkleblock.";
return false;
}
Expand Down Expand Up @@ -354,7 +356,13 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p

// Finally, validate peg-in via rpc call
if (check_depth && gArgs.GetBoolArg("-validatepegin", Params().GetConsensus().has_parent_chain)) {
if (!IsConfirmedBitcoinBlock(block_hash, Params().GetConsensus().pegin_min_depth, num_txs)) {
unsigned int required_depth = Params().GetConsensus().pegin_min_depth;
// Don't allow coinbase output claims before coinbase maturity
if (tx_index == 0) {
required_depth = std::max(required_depth, (unsigned int)COINBASE_MATURITY);
}
LogPrintf("Required depth: %d\n", required_depth);
if (!IsConfirmedBitcoinBlock(block_hash, required_depth, num_txs)) {
err_msg = "Needs more confirmations.";
return false;
}
Expand Down
6 changes: 5 additions & 1 deletion src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5240,7 +5240,11 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef

// Additional block lee-way to avoid bitcoin block races
if (gArgs.GetBoolArg("-validatepegin", Params().GetConsensus().has_parent_chain)) {
ret.pushKV("mature", IsConfirmedBitcoinBlock(merkleBlock.header.GetHash(), Params().GetConsensus().pegin_min_depth+2, merkleBlock.txn.GetNumTransactions()));
unsigned int required_depth = Params().GetConsensus().pegin_min_depth + 2;
if (txIndices[0] == 0) {
required_depth = std::max(required_depth, (unsigned int)COINBASE_MATURITY+2);
}
ret.pushKV("mature", IsConfirmedBitcoinBlock(merkleBlock.header.GetHash(), required_depth, merkleBlock.txn.GetNumTransactions()));
}

return ret;
Expand Down
51 changes: 37 additions & 14 deletions test/functional/feature_fedpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
get_datadir_path,
rpc_port,
p2p_port,
assert_raises_rpc_error,
assert_equal,
)
from decimal import Decimal

Expand Down Expand Up @@ -53,9 +55,10 @@ def setup_network(self, split=False):
else:
extra_args.extend([
"-validatepegin=0",
"-initialfreecoins=2100000000000000",
"-initialfreecoins=0",
"-anyonecanspendaremine",
"-signblockscript=51", # OP_TRUE
'-con_blocksubsidy=5000000000',
])

self.add_nodes(1, [extra_args], chain=[parent_chain], binary=parent_binary)
Expand All @@ -64,7 +67,6 @@ def setup_network(self, split=False):

connect_nodes_bi(self.nodes, 0, 1)
self.parentgenesisblockhash = self.nodes[0].getblockhash(0)
print('parentgenesisblockhash', self.parentgenesisblockhash)
if not self.options.parent_bitcoin:
parent_pegged_asset = self.nodes[0].getsidechaininfo()['pegged_asset']

Expand Down Expand Up @@ -147,24 +149,20 @@ def run_test(self):

addrs = sidechain.getpeginaddress()
addr = addrs["mainchain_address"]
print('addrs', addrs)
print(parent.getaddressinfo(addr))
txid1 = parent.sendtoaddress(addr, 24)
# 10+2 confirms required to get into mempool and confirm
parent.generate(1)
time.sleep(2)
proof = parent.gettxoutproof([txid1])

raw = parent.getrawtransaction(txid1)
print('raw', parent.getrawtransaction(txid1, True))

print("Attempting peg-in")
print("Attempting peg-ins")
# First attempt fails the consensus check but gives useful result
try:
pegtxid = sidechain.claimpegin(raw, proof)
raise Exception("Peg-in should not be mature enough yet, need another block.")
except JSONRPCException as e:
print('RPC ERROR:', e.error['message'])
assert("Peg-in Bitcoin transaction needs more confirmations to be sent." in e.error["message"])

# Second attempt simply doesn't hit mempool bar
Expand All @@ -173,14 +171,12 @@ def run_test(self):
pegtxid = sidechain.claimpegin(raw, proof)
raise Exception("Peg-in should not be mature enough yet, need another block.")
except JSONRPCException as e:
print('RPC ERROR:', e.error['message'])
assert("Peg-in Bitcoin transaction needs more confirmations to be sent." in e.error["message"])

try:
pegtxid = sidechain.createrawpegin(raw, proof, 'AEIOU')
raise Exception("Peg-in with non-hex claim_script should fail.")
except JSONRPCException as e:
print('RPC ERROR:', e.error['message'])
assert("Given claim_script is not hex." in e.error["message"])

# Should fail due to non-matching wallet address
Expand All @@ -189,7 +185,6 @@ def run_test(self):
pegtxid = sidechain.claimpegin(raw, proof, scriptpubkey)
raise Exception("Peg-in with non-matching claim_script should fail.")
except JSONRPCException as e:
print('RPC ERROR:', e.error['message'])
assert("Given claim_script does not match the given Bitcoin transaction." in e.error["message"])

# 12 confirms allows in mempool
Expand All @@ -208,7 +203,6 @@ def run_test(self):

tx1 = sidechain.gettransaction(pegtxid1)

print('tx1', tx1)
if "confirmations" in tx1 and tx1["confirmations"] == 6:
print("Peg-in is confirmed: Success!")
else:
Expand All @@ -222,7 +216,6 @@ def run_test(self):
vsize = decoded["vsize"]
fee_output = decoded["vout"][1]
fallbackfee_pervbyte = Decimal("0.00001")/Decimal("1000")
print("fee_output", fee_output)
assert fee_output["scriptPubKey"]["type"] == "fee"
assert fee_output["value"] >= fallbackfee_pervbyte*vsize

Expand All @@ -241,7 +234,7 @@ def run_test(self):
# Do multiple claims in mempool
n_claims = 6

print("Flooding mempool with many small claims")
print("Flooding mempool with a few claims")
pegtxs = []
sidechain.generate(101)

Expand Down Expand Up @@ -341,7 +334,6 @@ def run_test(self):

print("Restarting parent2")
self.start_node(1)
#parent2 = self.nodes[1]
connect_nodes_bi(self.nodes, 0, 1)

# Don't make a block, race condition when pegin-invalid block
Expand Down Expand Up @@ -390,10 +382,41 @@ def run_test(self):
# Make sure balance went down
assert(bal_2 + 1 < bal_1)

# Send rest of coins using subtractfee from output arg
sidechain.sendtomainchain(some_btc_addr, bal_2, True)

assert(sidechain.getwalletinfo()["balance"]['bitcoin'] == 0)

print('Test coinbase peg-in maturity rules')

# Have bitcoin output go directly into a claim output
pegin_info = sidechain.getpeginaddress()
mainchain_addr = pegin_info["mainchain_address"]
# Watch the address so we can get tx without txindex
parent.importaddress(mainchain_addr)
claim_block = parent.generatetoaddress(50, mainchain_addr)[0]
block_coinbase = parent.getblock(claim_block, 2)["tx"][0]
claim_txid = block_coinbase["txid"]
claim_tx = block_coinbase["hex"]
claim_proof = parent.gettxoutproof([claim_txid], claim_block)

# Can't claim something even though it has 50 confirms since it's coinbase
assert_raises_rpc_error(-8, "Peg-in Bitcoin transaction needs more confirmations to be sent.", sidechain.claimpegin, claim_tx, claim_proof)
# If done via raw API, still doesn't work
coinbase_pegin = sidechain.createrawpegin(claim_tx, claim_proof)
assert_equal(coinbase_pegin["mature"], False)
signed_pegin = sidechain.signrawtransactionwithwallet(coinbase_pegin["hex"])["hex"]
assert_raises_rpc_error(-26, "bad-pegin-witness, Needs more confirmations.", sidechain.sendrawtransaction, signed_pegin)

# 50 more blocks to allow wallet to make it succeed by relay and consensus
parent.generatetoaddress(50, parent.getnewaddress())
# Wallet still doesn't want to for 2 more confirms
assert_equal(sidechain.createrawpegin(claim_tx, claim_proof)["mature"], False)
# But we can just shoot it off
claim_txid = sidechain.sendrawtransaction(signed_pegin)
sidechain.generatetoaddress(1, sidechain.getnewaddress())
assert_equal(sidechain.gettransaction(claim_txid)["confirmations"], 1)

print('Success!')

# Manually stop sidechains first, then the parent chains.
Expand Down