Starting a node
Now that the node has been registered on the Greenlight server, we can schedule it. Scheduling will tell the scheduler that we want to interact with the node, and need its GRPC URI, so we can talk to it. The scheduler will look up the node, check if it is currently running, and if not it'll start the node. It will then return the URL you can use to connect to the node directly.
Important
Currently nodes will get a new address whenever they are started, so don't cache the URL for longer periods of time. We spin nodes down if there is no client talking to it, and slots are reused for other nodes. Attempting to talk to a node that isn't yours will fail to establish a connection.
The Greenlight team is working on an improvement that will assign a unique address to each node, ensuring that you always know how to reach the node, and allowing you to skip talking with the scheduler altogether.
First of all we build an instance of the scheduler service stub, which will allow us to call methods on the service. We then schedule the node, which returns a stub representing the node running on the Greenlight infrastructure:
let network = Network::Bitcoin;
let device_creds = Device::from_path(device_creds_path);
let scheduler = gl_client::scheduler::Scheduler::new(network, device_creds.clone())
.await
.unwrap();
let mut node: gl_client::node::ClnClient = scheduler.node().await.unwrap();
network = "bitcoin"
device_creds = Credentials.from_path(device_creds_path)
scheduler = Scheduler(network, device_creds)
node = scheduler.node()
Once we have an instance of the Node
we can start interacting with it via the GRPC interface:
let _info = node.getinfo(cln::GetinfoRequest::default()).await.unwrap();
let _peers = node
.list_peers(gl_client::pb::cln::ListpeersRequest::default())
.await
.unwrap();
info = node.get_info()
peers = node.list_peers()
The above snippet will read the metadata and list the peers from the node. Both of these are read-only operations, that do not require a signer to sign off. What happens if we issue a command that requires a signer to sign off? Let's try to connect to create an invoice. Invoices are signed using the node key, and the signer is the only component with access to your key.
let amount = AmountOrAny {
value: Some(amount_or_any::Value::Amount(Amount { msat: 10000 })),
};
node.invoice(cln::InvoiceRequest {
amount_msat: Some(amount),
description: "description".to_string(),
label: "label".to_string(),
..Default::default()
})
.await
.unwrap();
node.invoice(
amount_msat=clnpb.AmountOrAny(amount=clnpb.Amount(msat=10000)),
description="description",
label="label",
)
You'll notice that these calls hang indefinitely. This is because the
signer is not running and not attached to the node, and without its
signature we can't create the invoice. This isn't just the case for
the invoice
call either, all calls that somehow use the Node ID, or
move funds, will require the signer's sign-off. You can think of a
node without a signer being connected as a read-only node, and as soon
as you attach the signer, the node becomes fully functional. So how do
we attach the signer? Simple: load the secret from where you stored it
in the last chapter, instantiate the signer with it and then start it.
let seed = read_file("seed");
let signer = Signer::new(seed, network, device_creds.clone()).unwrap();
let (_tx, rx) = tokio::sync::mpsc::channel(1);
tokio::spawn(async move {
signer.run_forever(rx).await.unwrap();
});
Notice that signer.run_forever()
returns a Future
which you can spawn a
new task with. That is also the reason why a separate shutdown signal is
provided.
seed = read_file("seed")
signer = Signer(seed, network, device_creds)
signer.run_in_thread()
If you kept the stuck commands above running, you should notice that they now return a result. As mentioned before many RPC calls will need the signer to be attached to the node, so it's best to just start it early, and keep it running in the background whenever possible. The signer will not schedule the node by itself, instead waiting on the scheduler, so it doesn't consume much resources, but still be available when it is needed.