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

Changes required for running on the CTV signet. #3

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__pycache__
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,12 @@ $ cd simple-ctv-vault
$ pip install -r requirements.txt

# build this bitcoin branch
# https://github.com/JeremyRubin/bitcoin/tree/checktemplateverify-rebase-4-15-21
# https://github.com/JeremyRubin/bitcoin/tree/checktemplateverify-signet-23.0-alpha

# then start it on signet:
$ bitcoind -signet -signetchallenge=512102946e8ba8eca597194e7ed90377d9bbebc5d17a9609ab3e35e706612ee882759351ae -signetseednode=50.18.75.225 -txindex=1 &
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General feedback on this PR: we need to both support signet and regtest, so it would be useful to segment the various parts of this change out and keep regtest-specific content intact.


# or, for running on regtest:
$ bitcoind -regtest -txindex=1 &
```

Expand All @@ -85,14 +90,15 @@ Okay, we're ready to go.
### Creating a vault

```sh
$ TXID=$(./main.py vault)
$ ORIGINAL_COIN=$(./main.py vault)
```

![image](https://user-images.githubusercontent.com/73197/156897173-c8095fc6-ce39-47cf-85d7-3ac0f86ca2c8.png)


At this point, we've generated a coin on regtest and have spent it into a new vault.
`$TXID` corresponds to the transaction ID of the coin we spent into the vault,
At this point, we've generated a coin on signet and have spent it into a new vault.
`$ORIGINAL_COIN` corresponds to the transaction ID + output number of the coin we
spent into the vault,
which is the only piece of information we need to reconstruct the vault plan and
resume operations.

Expand Down
76 changes: 39 additions & 37 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@
./main.py to-cold $TXID

"""
import sys
import struct
import hashlib
import sys
import pprint
import typing as t
from dataclasses import dataclass

from bitcoin import SelectParams
from bitcoin.core import (
CTransaction,
CMutableTransaction,
Expand Down Expand Up @@ -72,13 +72,18 @@
SatsPerByte = int

TxidStr = str
UtxoStr = str
Txid = str
RawTxStr = str

# For use with template transactions.
BLANK_INPUT = CMutableTxIn


def no_output(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to revisit this since it definitely isn't well named as "no_output."

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was the name it had, I was very confused by it 😛



@dataclass(frozen=True)
class Coin:
outpoint: COutPoint
Expand All @@ -104,6 +109,8 @@ class Wallet:
coins: t.List[Coin]
network: str

log: t.Callable = no_output

@classmethod
def generate(cls, seed: bytes, network: str = "regtest") -> "Wallet":
return cls(
Expand All @@ -114,11 +121,12 @@ def generate(cls, seed: bytes, network: str = "regtest") -> "Wallet":

def fund(self, rpc: BitcoinRPC) -> Coin:
fund_addr = self.privkey.point.p2wpkh_address(network=self.network)
rpc.generatetoaddress(110, fund_addr)

if network == "regtest":
rpc.generatetoaddress(110, fund_addr)

scan = scan_utxos(rpc, fund_addr)
assert scan["success"]

for utxo in scan["unspents"]:
self.coins.append(
Coin(
Expand All @@ -129,17 +137,17 @@ def fund(self, rpc: BitcoinRPC) -> Coin:
)
)

if len(self.coins) == 0:
if self.network == "regtest":
self.log(
"Your regtest is out of subsidy - please wipe the datadir and restart."
)
else:
self.log(f"We need funds in {fund_addr}. Get some and try again.")
os.exit(1)

# Earliest coins first.
self.coins = [
c for c in sorted(self.coins, key=lambda i: i.height) if c.amount > COIN
]
try:
return self.coins.pop(0)
except IndexError:
raise RuntimeError(
"Your regtest is out of subsidy - "
"please wipe the datadir and restart."
)
self.coins = [c for c in sorted(self.coins, key=lambda i: i.height)]


@dataclass
Expand Down Expand Up @@ -454,10 +462,6 @@ def t_(b: t.Union[bytes, t.Any]) -> str:
bold = make_color(esc(1), esc(22))


def no_output(*args, **kwargs):
pass


@dataclass
class VaultExecutor:
plan: VaultPlan
Expand Down Expand Up @@ -619,7 +623,6 @@ class VaultScenario:

@classmethod
def from_network(cls, network: str, seed: bytes, coin: Coin = None, **plan_kwargs):
SelectParams(network)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self to verify that this doesn't actually do anything

from_wallet = Wallet.generate(b"from-" + seed)
fee_wallet = Wallet.generate(b"fee-" + seed)
cold_wallet = Wallet.generate(b"cold-" + seed)
Expand Down Expand Up @@ -648,20 +651,19 @@ def from_network(cls, network: str, seed: bytes, coin: Coin = None, **plan_kwarg
)

@classmethod
def for_demo(cls, original_coin_txid: TxidStr = None) -> 'VaultScenario':
def for_demo(cls, original_coin: UtxoStr = None) -> "VaultScenario":
"""
Instantiate a scenario for the demo, optionally resuming an existing
vault using the txid of the coin we spent into it.
"""
coin_in = None
if original_coin_txid:
if original_coin:
# We're resuming a vault
rpc = BitcoinRPC(net_name="regtest")
coin_in = Coin.from_txid(original_coin_txid, 0, rpc)
rpc = BitcoinRPC(net_name="signet")
txid, outn = original_coin.split(":")
coin_in = Coin.from_txid(txid, int(outn), rpc)

c = VaultScenario.from_network(
"regtest", seed=b"demo", coin=coin_in, block_delay=10
)
c = VaultScenario.from_network("signet", b"demo", coin=coin_in, block_delay=10)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded network constants should be factored out into some kind of SelectParams-ish global behavior that still allows use of regtest.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SelectParams wasn't being used for anything as far as I know, and you already had a global registry of ports for each network.

c.exec.log = lambda *args, **kwargs: print(*args, file=sys.stderr, **kwargs)

return c
Expand All @@ -677,22 +679,22 @@ def vault():

c.exec.send_to_vault(c.coin_in, c.from_wallet.privkey)
assert not c.exec.search_for_unvault()
original_coin_txid = c.coin_in.outpoint.hash[::-1].hex()
print(original_coin_txid)
original_coin = f"{c.coin_in.outpoint.hash[::-1].hex()}:{c.coin_in.outpoint.n}"
print(original_coin)


@cli.cmd
def unvault(original_coin_txid: TxidStr):
def unvault(original_coin: UtxoStr):
"""
Start the unvault process with an existing vault, based on the orignal coin
input.

We assume the original coin has a vout index of 0.

Args:
original_coin_txid: the txid of the original coin we spent into the vault.
original_coin: the txid of the original coin we spent into the vault.
"""
c = VaultScenario.for_demo(original_coin_txid)
c = VaultScenario.for_demo(original_coin)
c.exec.start_unvault()


Expand All @@ -703,30 +705,30 @@ def generate_blocks(n: int):


@cli.cmd
def alert_on_unvault(original_coin_txid: TxidStr):
c = VaultScenario.for_demo(original_coin_txid)
def alert_on_unvault(original_coin: UtxoStr):
c = VaultScenario.for_demo(original_coin)
c.exec.log = no_output
unvault_location = c.exec.search_for_unvault()

if unvault_location:
print(f"Unvault txn detected in {red(unvault_location)}!")
print(f"If this is unexpected, {red('sweep to cold now')} with ")
print(yellow(f"\n ./main.py to-cold {original_coin_txid}"))
print(yellow(f"\n ./main.py to-cold {original_coin}"))
sys.exit(1)


@cli.cmd
def to_hot(original_coin_txid: TxidStr):
def to_hot(original_coin: UtxoStr):
"""Spend funds to the hot wallet."""
c = VaultScenario.for_demo(original_coin_txid)
c = VaultScenario.for_demo(original_coin)
tx = c.exec.get_tohot_tx(c.hot_wallet.privkey)
_broadcast_final(c, tx, 'hot')


@cli.cmd
def to_cold(original_coin_txid: TxidStr):
def to_cold(original_coin: UtxoStr):
"""Sweep funds to the cold wallet."""
c = VaultScenario.for_demo(original_coin_txid)
c = VaultScenario.for_demo(original_coin)
tx = c.exec.get_tocold_tx()
_broadcast_final(c, tx, 'cold')

Expand Down