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

Peer channel split, so we can sanely allow a peer to reopen a channel while others are closed #975

Merged
merged 21 commits into from
Feb 14, 2018

Conversation

rustyrussell
Copy link
Contributor

This moves the internal structure closer to the db structure, where we separate peers and channels; conceptually closer to the final goal of having peers and channels be actual interface to the db (perhaps with a simple cache).

Much of the heavy lifting during the transition is done by channel2peer and peer2channel, to avoid a giant megapatch. These assume a 1:1 relationship between the two, which is true until the very final patch.

Signed-off-by: Rusty Russell <[email protected]>
Clearly we could do more damage if we continue.

Signed-off-by: Rusty Russell <[email protected]>
This avoids clashing with the new_channel we're about to add to lightningd,
and also matches its counterpart new_initial_channel.

Signed-off-by: Rusty Russell <[email protected]>
@ZmnSCPxj
Copy link
Contributor

Concept ACK

@rustyrussell rustyrussell force-pushed the peer-channel-split branch 3 times, most recently from ee37f2b to 4b79feb Compare February 12, 2018 02:57
This will be required to give it direct access to the ld->peers list.

Signed-off-by: Rusty Russell <[email protected]>
Both when we forget about an opening peer, and at startup.  We're
going to be relying on this, and the next patch, as we refactor
peer/channel handling to mirror the db.

Signed-off-by: Rusty Russell <[email protected]>
ON DELETE CASCADE goes the other way: we should clean up peers with no
channels from db.

Signed-off-by: Rusty Russell <[email protected]>
…annel.

In practice, it currently always does, so we've never hit an error.

Signed-off-by: Rusty Russell <[email protected]>
This is not connected yet; during the transition, there will be a 1:1
mapping from channel to peer, so we can use channel2peer and peer2channel
to shim between them.

Signed-off-by: Rusty Russell <[email protected]>
Much like the database; peer contains id, address, channel contains
per-channel information.  Where we create a channel, we always create
the peer too.

For the moment, peer->log and channel->log coexist side-by-side, to
reduce some of the churn.

Note that this changes the API to dev-forget-channel: if we have more
than one channel, we insist they specify the short-channel-id.

Signed-off-by: Rusty Russell <[email protected]>
Channels are within the peer structure, but the peer is freed only
when the last channel is freed.

We also implement channel_set_owner() and make peer_set_owner() a temporary
wrapper.

Signed-off-by: Rusty Russell <[email protected]>
And move them into channel.c.

Signed-off-by: Rusty Russell <[email protected]>
This rolls through many other functions, making them take channel not peer.

Signed-off-by: Rusty Russell <[email protected]>
And move the no-remaining-htlcs check from the peer destructor to the
channel destructor.

Signed-off-by: Rusty Russell <[email protected]>
This final sweep only keepl peer2channel within peer_control.c for
the reconnect case.

Signed-off-by: Rusty Russell <[email protected]>
And return the correct error message for the channel they give, if
they try to re-establish on an error channel.

Signed-off-by: Rusty Russell <[email protected]>
Copy link
Member

@cdecker cdecker left a comment

Choose a reason for hiding this comment

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

You really weren't joking when you said this is a large PR, but it's an excellent read 😉

I really don't like having DB operations hidden in destructors, they should always be explicit at the point we are freeing the memory and only if we really want to forget the channel for example.

NACK fefb026

@@ -181,6 +181,7 @@ static void free_peer(struct peer *peer, const char *why)
command_fail(peer->opening_cmd, "%s", why);
peer->opening_cmd = NULL;
}
wallet_channel_delete(peer->ld->wallet, peer->channel->id);
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't this also mean we delete all channels from the DB on a clean shutdown? That'd definitely be undesirable.

I prefer having an explicit call to wallet_channel_delete when we're sure we want to forget about the channel, rather than have this hidden DB change in a destructor anyway.

sqlite3_stmt *stmt;

/* Get rid of OPENINGD entries; they don't last across reconnects */
stmt = db_prepare(w->db, "DELETE FROM channels WHERE state=?");
Copy link
Member

Choose a reason for hiding this comment

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

Should really be a migration and OPENINGD channels should never be added again after the migration is applied.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, that's in a future patch. For the moment we still put opening channels in the DB, so this is needed.

wallet/wallet.c Outdated
{
sqlite3_stmt *stmt;
stmt = db_prepare(w->db, "DELETE FROM channels WHERE id=?");
/* FIXME: The line to clean up if we're last channel for peer would
Copy link
Member

Choose a reason for hiding this comment

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

Please no 😉 Let's not distribute business logic across more components.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, I see it the other way: eventually channel.c moves into wallet, and we use accessors. That makes it an internal in-memory cache.

But the comment was more about how we want on cascade delete in reverse, and whether we could do it better than this.

Copy link
Contributor

Choose a reason for hiding this comment

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

I somewhat agree with @rustyrussell here. I think we should accept SQLITE3 as our backend database, and, build subsystems that directly manipulate the DB while exposing a small number of functions that perform parts of business logic on the DB. xref. wallet/invoices.

Copy link
Member

Choose a reason for hiding this comment

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

Having triggers and caches is a bad idea, a trigger may be deleting the row in the DB while the cache still looks ok, so you'd be replicating the logic in C anyway to keep the cache coherent, or you can remove caches altogether, at which point I'd be reluctantly willing to move some business logic over to the DB.

I have yet to see a project that uses triggers and doesn't add complexity as a result of it, maybe this is the first, but I highly doubt it 😉

struct peer *peer;

/* Database ID: 0 == not in db yet */
u64 dbid;
Copy link
Member

Choose a reason for hiding this comment

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

Any reason we aren't just calling this id?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

id is used everywhere for node pubkey. This is much more explicit as to what it is.

command_fail(channel->opening_cmd, "%s", why);
channel->opening_cmd = NULL;
}
wallet_channel_delete(channel->peer->ld->wallet, channel->dbid,
Copy link
Member

Choose a reason for hiding this comment

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

Here we again use a destructor to actually delete stuff in the DB. This is bad IMHO since the lifetime in memory is not the same as the lifetime in the DB. If we do a clean shutdown we may end up deleting all channels from the DB...

I have a strong preference for explicit DB operations rather than hiding them in destructors.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's not the destructor. The destructors are all called destroy_xxx for that reason.

TBH I wanted the destructor-deletes semantic, but as you point out, shutdown would clean our db:)

Copy link
Contributor

Choose a reason for hiding this comment

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

The destructors are all called destroy_xxx for that reason.

This surprises me. There are a few destructors that are not named by this convention (remove_timer, invoice_waiter_dtor, free_subd_req, remove_unstored_payment). How strict is this rule, and should we have a cleanup issue for this?

Copy link
Member

Choose a reason for hiding this comment

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

Ok, then I propose renaming free_channel to delete_peer, since free_* suggests a memory operation to me :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed, will push on top.

And will push destroy renaming too...

free_channel() sounds like a destructor.

Suggested-by: @cdecker
Signed-off-by: Rusty Russell <[email protected]>
@rustyrussell
Copy link
Contributor Author

OK, all feedback should be addressed now! Thanks!

@rustyrussell rustyrussell force-pushed the peer-channel-split branch 3 times, most recently from 12c60a7 to 25753aa Compare February 14, 2018 01:24
This provides a sanity check that we are in sync, and also keeps the
logic in the program and out of the SQL.

Since the destructor now doesn't clean up the peer, there are some
wider changes to be made when cleaning up.  Most notably we create
lots of channels in run-wallet.c and they previously freed the peer:
now we need free the peer explicitly, so we need to free them first.

Suggested-by: @cdecker
Signed-off-by: Rusty Russell <[email protected]>
We usually did this, but sometimes they were named after what they did,
rather than what they cleaned up.

There are still a few exceptions:
1. I didn't bother creating destroy_xxx wrappers for htable routines
   which already existed.
2. Sometimes destructors really are used for side-effects (eg. to simply
   mark that something was freed): these are clearer with boutique names.
3. Generally destructors are static, but they don't need to be: in some
   cases we attach a destructor then remove it later, or only attach
   to *some* cases.  These are best with qualifiers in the destroy_<type>
   name.

Suggested-by: @ZmnSCPxj
Signed-off-by: Rusty Russell <[email protected]>
@rustyrussell rustyrussell mentioned this pull request Feb 14, 2018
@cdecker cdecker dismissed their stale review February 14, 2018 10:31

Changed as requested

@cdecker
Copy link
Member

cdecker commented Feb 14, 2018

ACK 383afe8

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants