Skip to content

Commit

Permalink
Added python getting-started example.
Browse files Browse the repository at this point in the history
  • Loading branch information
Randy808 committed Apr 29, 2024
1 parent 17d315d commit df2061f
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 89 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ cln-versions/lightningd-%.tar.bz2:
cln: ${CLN_TARGETS}

docs:
mypy examples/python
cargo build --manifest-path=./examples/rust/getting-started/Cargo.toml
mkdir -p ${REPO_ROOT}/site/
(cd docs; mkdocs build --strict --clean --site-dir=${REPO_ROOT}/site/ --verbose)
pdoc -o site/py glclient
Expand Down
2 changes: 1 addition & 1 deletion docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ markdown_extensions:
strip_comments: true
strip_js_on_attributes: false
- pymdownx.snippets:
base_path: ["../examples/rust/getting-started/src"]
base_path: ["../examples/rust/getting-started/src", "../examples/python/getting-started/"]
theme:
name: material
logo: assets/logo.png
Expand Down
15 changes: 1 addition & 14 deletions docs/src/getting-started/recover.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,7 @@ In order to recover access all you need to do is recover the `seed` from the BIP

=== "Python"
```python
from glclient import Scheduler, Signer, TlsConfig

cert = ... // Your developer certificate
key = ... // Your developer key
seed = ... // Load seed from file

tls = TlsConfig().identity(cert, key);
signer = Signer(seed, network="bitcoin", tls=tls)
scheduler = Scheduler(
node_id=signer.node_id(),
network="bitcoin",
tls=tls,
)
res = scheduler.recover(signer)
--8<-- "main.py:recover_node"
```

Notice that we are using a `TlsConfig` that is not configured with a
Expand Down
43 changes: 5 additions & 38 deletions docs/src/getting-started/register.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,7 @@ phrase and then convert it into a seed secret we can use:
=== "Python"

```python
import bip39
import secrets # Make sure to use cryptographically sound randomness

rand = secrets.randbits(256).to_bytes(32, 'big') # 32 bytes of randomness
phrase = bip39.encode_bytes(rand)

# Prompt user to safely store the phrase

seed = bip39.phrase_to_seed(phrase)[:32] # Only need 32 bytes

# Store the seed on the filesystem, or secure configuration system
--8<-- "main.py:create_seed"
```

!!! important
Expand All @@ -89,12 +79,7 @@ nodes.
=== "Python"

```python
from glclient import TlsConfig

# Creating a new `TlsConfig` object using your developer certificate
# - cert: contains the content of `client.crt`
# - key: contains the content of `client-key.pem`
tls = TlsConfig().identity(cert, key)
--8<-- "main.py:dev_creds"
```


Expand All @@ -115,9 +100,7 @@ We'll pick `bitcoin`, because ... reckless 😉

=== "Python"
```python
from glclient import Signer

signer = Signer(seed, network="bitcoin", tls=tls)
--8<-- "main.py:init_signer"
```

[bip39]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
Expand All @@ -140,17 +123,7 @@ node's private key. Since the private key is managed exclusively by the

=== "Python"
```python
from glclient import Scheduler

scheduler = Scheduler(
node_id=signer.node_id(),
network="bitcoin",
tls=tls,
)

# Passing in the signer is required because the client needs to prove
# ownership of the `node_id`
res = scheduler.register(signer)
--8<-- "main.py:register_node"
```

The result of `register` contains the credentials that can be used
Expand All @@ -167,13 +140,7 @@ going forward to talk to the scheduler and the node itself.

=== "Python"
```python
tls = TlsConfig().identity(res.device_cert, res.device_key)

# Use the configured `tls` instance when creating `Scheduler` and `Signer`
# instance going forward
signer = Signer(seed, network="bitcoin", tls=tls)
scheduler = Scheduler(node_id=signer.node_id(), network="bitcoin", tls=tls)
node = scheduler.node()
--8<-- "main.py:get_node"
```

If you get an error about a certificate verification failure when
Expand Down
26 changes: 4 additions & 22 deletions docs/src/getting-started/schedule.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,7 @@ Greenlight infrastructure:

=== "Python"
```python
from glclient import TlsConfig, Scheduler
cert, key = b'...', b'...'
node_id = bytes.fromhex("02058e8b6c2ad363ec59aa136429256d745164c2bdc87f98f0a68690ec2c5c9b0b")
network = "testnet"
tls = TlsConfig().identity(cert, key)

scheduler = Scheduler(node_id, network, tls)
node = scheduler.node()
--8<-- "main.py:start_node"
```

Once we have an instance of the `Node` we can start interacting with it via the GRPC interface:
Expand All @@ -49,8 +42,7 @@ Once we have an instance of the `Node` we can start interacting with it via the
```
=== "Python"
```python
info = node.get_info()
peers = node.list_peers()
--8<-- "main.py:list_peers"
```

The above snippet will read the metadata and list the peers from the
Expand All @@ -67,12 +59,7 @@ only component with access to your key.

=== "Python"
```python
from glclient import clnpb
node.invoice(
amount_msat=clnpb.AmountOrAny(any=True),
label="label",
description="description",
)
--8<-- "main.py:create_invoice"
```

You'll notice that these calls hang indefinitely. This is because the
Expand All @@ -96,12 +83,7 @@ in the last chapter, instantiate the signer with it and then start it.

=== "Python"
```python
seed = ... # Load from wherever you stored it
cert, key = ... // Load the cert and key you got from the `register` call

tls = TlsConfig().identity(device_cert, device_key)
signer = Signer(seed, network="bitcoin", tls=tls)
signer.run_in_thread()
--8<-- "main.py:start_signer"
```

If you kept the stuck commands above running, you should notice that
Expand Down
15 changes: 15 additions & 0 deletions examples/python/getting-started/ca.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIICYjCCAgigAwIBAgIUDEw2osNBr+H1o4WCvPSRIjNzUzQwCgYIKoZIzj0EAwIw
fjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh
biBGcmFuY2lzY28xFDASBgNVBAoTC0Jsb2Nrc3RyZWFtMR0wGwYDVQQLExRDZXJ0
aWZpY2F0ZUF1dGhvcml0eTENMAsGA1UEAxMER0wgLzAeFw0yMTA0MjYxNzE0MDBa
Fw0zMTA0MjQxNzE0MDBaMH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9y
bmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRQwEgYDVQQKEwtCbG9ja3N0cmVh
bTEdMBsGA1UECxMUQ2VydGlmaWNhdGVBdXRob3JpdHkxDTALBgNVBAMTBEdMIC8w
WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATp83k4SqQ5geGRpIpDuU20vrZz8qJ8
eBDYbW3nIlC8UM/PzVBSNA/MqWlAamB3YGK+VlgsEMbeOUWEM4c9ztVlo2QwYjAO
BgNVHQ8BAf8EBAMCAaYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIG
A1UdEwEB/wQIMAYBAf8CAQMwHQYDVR0OBBYEFM6ha+o75cd25WbWGqXGR1WKTikj
MAoGCCqGSM49BAMCA0gAMEUCIGBkjyp1Nd/m/b3jEAUmxAisqCahuQUPuyQrIwo0
ZF/9AiEAsZ8qZfkUZH2Ya7y6ccFTDps/ahsFWSrRao8ru3yhhrs=
-----END CERTIFICATE-----
126 changes: 126 additions & 0 deletions examples/python/getting-started/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import bip39 # type: ignore
from glclient import Credentials, Signer, Scheduler # type: ignore
from pathlib import Path
from pyln import grpc as clnpb # type: ignore
import secrets # Make sure to use cryptographically sound randomness


def save_to_file(file_name: str, data: bytes) -> None:
with open(file_name, "wb") as file:
file.write(data)


def read_file(file_name: str) -> bytes:
with open(file_name, "rb") as file:
return file.read()


def create_seed() -> bytes:
# ---8<--- [start: create_seed]
rand = secrets.randbits(256).to_bytes(32, "big") # 32 bytes of randomness

# Show seed phrase to user
phrase = bip39.encode_bytes(rand)

seed = bip39.phrase_to_seed(phrase)[:32] # Only need the first 32 bytes

# Store the seed on the filesystem, or secure configuration system
save_to_file("seed", seed)

# ---8<--- [end: create_seed]
return seed


def nobody_with_identity(developer_cert: bytes, developer_key: bytes) -> Credentials:
ca = Path("ca.pem").open(mode="rb").read()
return Credentials.nobody_with(developer_cert, developer_key, ca)


Credentials.nobody_with_identity = nobody_with_identity


# Validated against gl-testing
def register_node(seed: bytes, developer_cert_path: str, developer_key_path: str) -> None:
# ---8<--- [start: dev_creds]
developer_cert = Path(developer_cert_path).open(mode="rb").read()
developer_key = Path(developer_key_path).open(mode="rb").read()

developer_creds = Credentials.nobody_with_identity(developer_cert, developer_key)
# ---8<--- [end: dev_creds]

# ---8<--- [start: init_signer]
network = "bitcoin"
signer = Signer(seed, network, developer_creds)
# ---8<--- [end: init_signer]

# ---8<--- [start: register_node]
scheduler = Scheduler(signer.node_id(), network, developer_creds)

# Passing in the signer is required because the client needs to prove
# ownership of the `node_id`
registration_response = scheduler.register(signer, invite_code=None)

device_creds = Credentials.from_bytes(registration_response.creds)
# save_to_file("creds", device_creds.to_bytes());
# ---8<--- [end: register_node]

# ---8<--- [start: get_node]
scheduler = scheduler.authenticate(device_creds)
node = scheduler.node()
# ---8<--- [end: get_node]


# TODO: Remove node_id from signature and add node_id to credentials
def start_node(device_creds_path: str, node_id: bytes) -> None:
# ---8<--- [start: start_node]
network = "bitcoin"
device_creds = Credentials.from_path(device_creds_path)
scheduler = Scheduler(node_id, network, device_creds)

node = scheduler.node()
# ---8<--- [end: start_node]

# ---8<--- [start: list_peers]
info = node.get_info()
peers = node.list_peers()
# ---8<--- [end: list_peers]

# ---8<--- [start: start_signer]
seed = read_file("seed")
signer = Signer(seed, network, device_creds)

signer.run_in_thread()
# ---8<--- [end: start_signer]

# ---8<--- [start: create_invoice]
node.invoice(
amount_msat=clnpb.AmountOrAny(amount=clnpb.Amount(msat=10000)),
description="description",
label="label",
)
# ---8<--- [end: create_invoice]


def recover_node(device_cert: bytes, device_key: bytes) -> None:
# ---8<--- [start: recover_node]
seed = read_file("seed")
network = "bitcoin"
signer_creds = Credentials.with_identity(device_cert, device_key)
signer = Signer(seed, network, signer_creds)

scheduler = Scheduler(
signer.node_id(),
network,
signer_creds,
)

scheduler_creds = signer_creds.upgrade(signer, scheduler)

scheduler = Scheduler(
signer.node_id(),
network,
scheduler_creds,
)

scheduler.recover(signer)
# ---8<--- [end: recover_node]
Loading

0 comments on commit df2061f

Please sign in to comment.