Skip to content

Commit

Permalink
factor out make_reserves_proof() for other usage
Browse files Browse the repository at this point in the history
  • Loading branch information
shuckc committed Sep 14, 2023
1 parent 876e192 commit 80e65f5
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 103 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ succeeding eventually.

Sample output from running PoR tool on testnet dataset against testnet bitcoind RPC server.
```
$ python3 validate_reserves.py --proof testnet_reserves.yaml --bitcoind testnet://username:password@localhost:18332
$ python3 validate_reserves.py --proof testnet_reserves.yaml --bitcoin testnet://username:password@localhost:18332
...
WARNING:root:Proof of Reserves on pruned nodes not well-supported. Node can get stuck reorging past pruned blocks.
INFO:root:Bitcoind alive: At block 1938810
Expand All @@ -69,7 +69,7 @@ Proven amount(BTC): 205393.049391
INFO:root:IMPORTANT! Call this script with --reconsider to bring your bitcoin node back to tip when satisfied with the results
# Run --reconsider to re-set your node to undo block invalidations, getting your node back to chaintip
$ python3 validate_reserves.py --reconsider --bitcoind testnet://username:password@localhost:18332
$ python3 validate_reserves.py --reconsider --bitcoin testnet://username:password@localhost:18332
INFO:root:Reconsidering blocks and exiting.
```

Expand Down
192 changes: 99 additions & 93 deletions test/test_reserves.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,92 @@ def wait_until_alive(self):
logging.info("Bitcoin server not responding, sleeping for retry.")


def make_reserves_proof(bitcoin, dep_sizes=["0.0001", "0.00004", "0.000007"]):
logging.info("Generating a regtest PoR file, and running it through the validator")
assert bitcoin.getbalance([]) == 0

# Generate 3 3-of-4 addresses using 3 static pubkeys:
# 0) legacy uncompressed
# 1) nested segwit
# 2) native segwit

# testnet keys
static_uncompressed_keys = [
"04ceba29da1af96a0f2ef7cda6950b8be2baeb1adf12c0d5efebb70dbcaa086ba029cd82a0cfb8dedf65b8760cf271f2b8a50466bbf0b9339c5ffefbe2a4165326",
"04d5a42b90e9d7156155661979530a09d2e12e252ef4104e5611274a7ae7e2b0940657307b129bef948ea932d2d3f20e1a0513c9e84fd850f743ee66a2e3348d1f",
"04c10be2f0dc20f4285c25156aa22a0c46d2b89ccc4d1c8eaed92ea0c1a8f40c0003c26eda3bba9d087d0c521327fa1f0426ca510147957c8e342527ce7d9d0048",
]
static_compressed_keys = [
"02ceba29da1af96a0f2ef7cda6950b8be2baeb1adf12c0d5efebb70dbcaa086ba0",
"03d5a42b90e9d7156155661979530a09d2e12e252ef4104e5611274a7ae7e2b094",
"02c10be2f0dc20f4285c25156aa22a0c46d2b89ccc4d1c8eaed92ea0c1a8f40c00",
]

legacy_key = "04ffeec30e5b7657f12f52249e6ab282e768a7a829b79213850af60121dea49fd230598677929e637beed9624bfcfb62721ff7f14a88d996521520651c993e3f41"
nested_key = "025a2edfd78a4d8bba9616170a02bc61736020d66e447d4501130148cc0fcb5b24"
native_key = "03534341220e385e2d9ac1db696dceb3db48f96ed1d2718393302e7fe88f78976f"

legacy = bitcoin.createmultisig(
[3, static_uncompressed_keys + [legacy_key], "legacy"],
)
nested = bitcoin.createmultisig(
[3, static_compressed_keys + [nested_key], "p2sh-segwit"],
)
native = bitcoin.createmultisig(
[3, static_compressed_keys + [native_key], "bech32"],
)

# Deposit some specific amounts for testing the utxo scanning
gen_addr = bitcoin.getnewaddress([])
bitcoin.generatetoaddress([101, gen_addr])

dep_sizes = [Decimal(x) for x in dep_sizes]

for addr, dep_size in zip(
[legacy["address"], nested["address"], native["address"]], dep_sizes
):
bitcoin.sendtoaddress([addr, str(dep_size)])
gen_addr = bitcoin.getnewaddress([])
bitcoin.generatetoaddress([1, gen_addr])

# This is where validator will check in history
proof_height = bitcoin.getblockcount([])

# Do another deposit *above* the proof height to make sure this isn't found by the validator script
bitcoin.sendtoaddress([native["address"], 1])

# now mine blocks above the proof's height
gen_addr = bitcoin.getnewaddress([])
bitcoin.generatetoaddress([10, gen_addr])

# Construct the proof, tool takes uncompressed version and convertes internally
proof = {
"height": proof_height,
"chain": "regtest",
"claim": {"m": 3, "n": 4},
"total": float(sum(dep_sizes)),
"keys": static_uncompressed_keys,
}
proof["address"] = [
{
"addr_type": "sh",
"addr": legacy["address"],
"script": legacy["redeemScript"],
},
{
"addr_type": "sh_wsh",
"addr": nested["address"],
"script": nested["redeemScript"],
},
{
"addr_type": "wsh",
"addr": native["address"],
"script": native["redeemScript"],
},
]
return proof


class TestReserves(unittest.TestCase):
@classmethod
def setUpClass(self):
Expand Down Expand Up @@ -94,96 +180,17 @@ def tearDownClass(self):
os.waitpid(self.regtest_bitcoind_proc.pid, 0)

def test_reserves(self):
logging.info(
"Generating a regtest PoR file, and running it through the validator"
)
self.bitcoin.getbalance([]) == 0

# Generate 3 3-of-4 addresses using 3 static pubkeys:
# 0) legacy uncompressed
# 1) nested segwit
# 2) native segwit

# testnet keys
static_uncompressed_keys = [
"04ceba29da1af96a0f2ef7cda6950b8be2baeb1adf12c0d5efebb70dbcaa086ba029cd82a0cfb8dedf65b8760cf271f2b8a50466bbf0b9339c5ffefbe2a4165326",
"04d5a42b90e9d7156155661979530a09d2e12e252ef4104e5611274a7ae7e2b0940657307b129bef948ea932d2d3f20e1a0513c9e84fd850f743ee66a2e3348d1f",
"04c10be2f0dc20f4285c25156aa22a0c46d2b89ccc4d1c8eaed92ea0c1a8f40c0003c26eda3bba9d087d0c521327fa1f0426ca510147957c8e342527ce7d9d0048",
]
static_compressed_keys = [
"02ceba29da1af96a0f2ef7cda6950b8be2baeb1adf12c0d5efebb70dbcaa086ba0",
"03d5a42b90e9d7156155661979530a09d2e12e252ef4104e5611274a7ae7e2b094",
"02c10be2f0dc20f4285c25156aa22a0c46d2b89ccc4d1c8eaed92ea0c1a8f40c00",
]

legacy_key = "04ffeec30e5b7657f12f52249e6ab282e768a7a829b79213850af60121dea49fd230598677929e637beed9624bfcfb62721ff7f14a88d996521520651c993e3f41"
nested_key = (
"025a2edfd78a4d8bba9616170a02bc61736020d66e447d4501130148cc0fcb5b24"
)
native_key = (
"03534341220e385e2d9ac1db696dceb3db48f96ed1d2718393302e7fe88f78976f"
)

legacy = self.bitcoin.createmultisig(
[3, static_uncompressed_keys + [legacy_key], "legacy"],
)
nested = self.bitcoin.createmultisig(
[3, static_compressed_keys + [nested_key], "p2sh-segwit"],
)
native = self.bitcoin.createmultisig(
[3, static_compressed_keys + [native_key], "bech32"],
)

# Deposit some specific amounts for testing the utxo scanning
gen_addr = self.bitcoin.getnewaddress([])
self.bitcoin.generatetoaddress([101, gen_addr])
dep_sizes = [Decimal("0.0001"), Decimal("0.00004"), Decimal("0.000007")]
for addr, dep_size in zip(
[legacy["address"], nested["address"], native["address"]], dep_sizes
):
self.bitcoin.sendtoaddress([addr, str(dep_size)])
gen_addr = self.bitcoin.getnewaddress([])
self.bitcoin.generatetoaddress([1, gen_addr])

# This is where validator will check in history
proof_height = self.bitcoin.getblockcount([])
proof_hash = self.bitcoin.getblockhash([proof_height])

# Do another deposit to make sure this isn't found by the validator script
self.bitcoin.sendtoaddress([native["address"], 1])
gen_addr = self.bitcoin.getnewaddress([])
last_blocks = self.bitcoin.generatetoaddress([10, gen_addr])
total_height = proof_height + 10

# Construct the proof, tool takes uncompressed version and convertes internally
proof = {
"height": proof_height,
"chain": "regtest",
"claim": {"m": 3, "n": 4},
"total": float(sum(dep_sizes)),
"keys": static_uncompressed_keys,
}
proof["address"] = [
{
"addr_type": "sh",
"addr": legacy["address"],
"script": legacy["redeemScript"],
},
{
"addr_type": "sh_wsh",
"addr": nested["address"],
"script": nested["redeemScript"],
},
{
"addr_type": "wsh",
"addr": native["address"],
"script": native["redeemScript"],
},
]

proof = make_reserves_proof(self.bitcoin)
with open("test.proof", "w") as f:
yaml.dump(proof, f)

proof_hash = self.bitcoin.getblockhash([proof["height"]])

# check again that validation will require some re-winding
tip_height = self.bitcoin.getblockcount([])
tip_hash = self.bitcoin.getblockhash([tip_height])
assert proof["height"] < tip_height

# Run validator tool against the proof file
run_args = [
"python",
Expand All @@ -200,11 +207,11 @@ def test_reserves(self):
# Check output file's value
with open(proof_hash + "_result.json") as f:
result = json.load(f)
self.assertEqual(str(result["amount_proven"]), str(sum(dep_sizes)))
self.assertEqual(str(result["amount_claimed"]), str(sum(dep_sizes)))
self.assertEqual(str(result["amount_proven"]), str(proof["total"]))
self.assertEqual(str(result["amount_claimed"]), str(proof["total"]))

# Check that blockheight looks right
self.assertEqual(self.bitcoin.getblockcount([]), proof_height)
self.assertEqual(self.bitcoin.getblockcount([]), proof["height"])

# --reconsider call to make sure that it resets blockheight of the node, don't use rpchost to check default
run_args = [
Expand All @@ -215,10 +222,9 @@ def test_reserves(self):
"--reconsider",
]
output = subprocess.check_output(run_args).decode("utf-8")
while self.bitcoin.getblockcount([]) != total_height:
while self.bitcoin.getblockcount([]) != tip_height:
time.sleep(0.1)

self.assertEqual(self.bitcoin.getbestblockhash([]), last_blocks[-1])
self.assertEqual(self.bitcoin.getbestblockhash([]), tip_hash)

# check rejection of proofs containing duplicate addresses/scripts
proof["address"].append(proof["address"][0])
Expand Down
13 changes: 5 additions & 8 deletions validate_reserves.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def wait_until_alive(self):
self.version = self.getnetworkinfo([])["version"]
break
except Exception as e:
logging.info("Bitcoin server not responding, sleeping for retry.")
logging.exception("Bitcoin server not responding, sleeping for retry.")


def read_proof_file(proof_file):
Expand Down Expand Up @@ -337,12 +337,9 @@ def validate_proofs(bitcoin, proof_data):

proven_amount += res["total_amount"]

if args.verbose:
logging.info(res)

logging.info(
"***RESULTS***\nHeight of proof: {}\nBlock proven against: {}\nProven amount(BTC): {}".format(
proof_data["height"], block_hash, proven_amount
"***RESULTS***\nHeight of proof: {}\nBlock proven against: {}\nClaimed amount (BTC): {}\nProven amount(BTC): {}".format(
proof_data["height"], block_hash, proof_data["total"], proven_amount
)
)
return {
Expand Down Expand Up @@ -380,7 +377,7 @@ def reconsider_blocks(bitcoin):
action="store_true",
)
parser.add_argument(
"--bitcoind", default=BITCOIND_DEFAULT, help="Override bitcoind URI"
"--bitcoin", default=BITCOIND_DEFAULT, help="Override bitcoind URI"
)
parser.add_argument(
"--verbose", "-v", help="Prints more information about scanning results"
Expand All @@ -391,7 +388,7 @@ def reconsider_blocks(bitcoin):
)
args = parser.parse_args()

bitcoin = BitcoinRPC(args.bitcoind)
bitcoin = BitcoinRPC(args.bitcoin)
logging.getLogger().setLevel(logging.INFO)

bitcoin.wait_until_alive()
Expand Down

0 comments on commit 80e65f5

Please sign in to comment.