Skip to content
This repository has been archived by the owner on Apr 23, 2024. It is now read-only.

sdk/state: refactor validations to check against the channel state #270

Closed
wants to merge 15 commits into from

Conversation

acharb
Copy link
Contributor

@acharb acharb commented Aug 30, 2021

WHAT
changes the validations for checking if the channel is open to use the Channel State instead of checking the openAgreement is empty or not

WHY
the channel state better reflects if the channel is actually open based off of ingested transactions. And more accurately gives the state of the channel.

closes #261

@acharb acharb changed the title (wip) sdk/state: refactor validations to check against the channel state sdk/state: refactor validations to check against the channel state Aug 31, 2021
if err != nil {
return fmt.Errorf("getting channel state: %w", err)
}
if cs >= StateOpen {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this is better than if cs != StateNone, in the case the channel errored on the first attempt to open, so they should be allowed to try again

Copy link
Contributor

Choose a reason for hiding this comment

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

in the case the channel errored on the first attempt to open, so they should be allowed to try again

❗ I think there could be cases where StateFailed is the state returned but the current openAgreement could still be valid. So I'm don't think we should allow trying again and consider the state.Channel one shot. It could be functionality up the stack in the Agent or higher that allows retrying, by destroying the state.Channel and creating a new one.

sdk/state/payment.go Outdated Show resolved Hide resolved
Copy link
Contributor

@leighmcculloch leighmcculloch left a comment

Choose a reason for hiding this comment

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

I left some ideas (💡), concerns (🤔), notes about things that need addressing (❗). Because I raised some concerns about using the state check in some places, I explicitly marked the places where the state looks good (👍🏻).

The improvements to the tests look great. 🎉

Comment on lines +199 to +200
if cs >= StateOpen {
return OpenAgreement{}, fmt.Errorf("cannot propose a new open if channel has already opened")
Copy link
Contributor

Choose a reason for hiding this comment

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

❗ I think we're missing catching StateFailed? StateFailed is < StateOpen so will not be caught here and so the open will be allowed. I think we should err on the side of allowing only valid scenarios, so:

Suggested change
if cs >= StateOpen {
return OpenAgreement{}, fmt.Errorf("cannot propose a new open if channel has already opened")
if cs != StateNone {
return OpenAgreement{}, fmt.Errorf("cannot propose a new open if channel has already opened")

🤔 Similar to my other two comments above I think this changes the condition to be a little more disconnected from the needs of the function. In this case the function is protecting against replacing an c.openAgreement that already exists. We're replacing that protective guard with one about the state, that indirectly protects against replacing the openAgreement.

❗ Note that there's actually a bug in this function in the original code. The original condition should have been !c.openAgreement.isEmpty() because we should never allow proposing two opens because that could result in us losing control of the channel. If we make that fix I don't think we can reproduce that condition with the state check anymore, because the state would be StateNone, so I'm thinking we need to keep the open agreement condition instead, or maybe 💡 we need more states, such as StateOpenProposed, StateOpenConfirmed. That might not be a bad idea.

For example:

  • StateFailed
  • StateOpenProposed = only one signer on the open
  • StateOpenConfirmed = two signers, not ingested
  • StateOpened = ingested, actually opened on network
  • StateClosing
  • StateClosed

❓ Wdyt about keeping the original condition and adding the state condition with new states? The original condition is defensive to make sure we don't replace data we need, or that data exists before we use it. The state condition is more informative.

Comment on lines +96 to 101
cs, err := c.State()
if err != nil {
return CloseAgreement{}, fmt.Errorf("getting channel state: %w", err)
}
if cs < StateOpen {
return CloseAgreement{}, fmt.Errorf("cannot propose a coordinated close before channel is opened")
Copy link
Contributor

Choose a reason for hiding this comment

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

🤔 Looking back on this guard, the comment says to check the channel is open, and the same with the error, but I think the condition that was there before was more valuable and more precise than the new condition on the state. This function needs a latestAuthorizedCloseAgreement, and so the guard that was here was to make sure that existed. In some ways the guard has become less specific, and a little disconnected from what the function needs.

This makes me think we should keep the existing guard but change the error. For example:

Suggested change
cs, err := c.State()
if err != nil {
return CloseAgreement{}, fmt.Errorf("getting channel state: %w", err)
}
if cs < StateOpen {
return CloseAgreement{}, fmt.Errorf("cannot propose a coordinated close before channel is opened")
if c.latestAuthorizedCloseAgreement.isEmpty() {
return CloseAgreement{}, fmt.Errorf("cannot propose a coordinated close without an authorized close agreement to amend")

This makes the requirements of the function clear, albeit leaks some details to the user that aren't helpful since it would be more helpful to tell the user that the channel isn't open, in which case maybe the existing error is fine.

Wdyt?

Wdyt of my suggestion in the comment above?

Copy link
Contributor Author

@acharb acharb Sep 1, 2021

Choose a reason for hiding this comment

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

🤔 in my head I was thinking that, by checking the channel is in the correct state for proposing/confirming a close, then we're checking the prerequisites for doing the action are completed. So in this case checking the state means we're also checking the channel went through the open steps, latestAuthorizedAgreement looks good (or at least it's not empty), and sequence number is in the right place. I liked that better than explicitly checking the data for these propose/confirm methods.

I guess it's the difference of checking the data explicitly vs an abstracted method here, which is more disconnected 🤔 . wdyt, I might not be thinking of something so if you think the former is better I can change back here

Comment on lines -125 to +136
if c.latestAuthorizedCloseAgreement.isEmpty() {
cs, err := c.State()
if err != nil {
return fmt.Errorf("getting channel state: %w", err)
}
if cs < StateOpen {
return fmt.Errorf("cannot confirm a coordinated close before channel is opened")
}

Copy link
Contributor

Choose a reason for hiding this comment

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

🤔 The same thing here too. This guard on c.latestAuthorizedCloseAgreement protects against our use of that value immediately following, but that direct relation is lost with a change to a guard on the state.

if err != nil {
return fmt.Errorf("getting channel state: %w", err)
}
if cs >= StateOpen {
Copy link
Contributor

Choose a reason for hiding this comment

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

in the case the channel errored on the first attempt to open, so they should be allowed to try again

❗ I think there could be cases where StateFailed is the state returned but the current openAgreement could still be valid. So I'm don't think we should allow trying again and consider the state.Channel one shot. It could be functionality up the stack in the Agent or higher that allows retrying, by destroying the state.Channel and creating a new one.

Comment on lines -99 to +103
if c.latestAuthorizedCloseAgreement.isEmpty() {
cs, err := c.State()
if err != nil {
return CloseAgreement{}, fmt.Errorf("getting channel state: %w", err)
}
if cs != StateOpen {
Copy link
Contributor

Choose a reason for hiding this comment

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

👍🏻 I think this change to using state is good.

Comment on lines -161 to +169
if c.latestAuthorizedCloseAgreement.isEmpty() {
cs, err := c.State()
if err != nil {
return fmt.Errorf("getting channel state: %w", err)
}
if cs < StateOpen {
Copy link
Contributor

Choose a reason for hiding this comment

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

👍🏻 I think using the state here is good, however I think the condition needs to be cs != StateOpen, because any non-open state should not be supported here.

ResponderSigner: remoteSigner.Address(),
InitiatorEscrow: localEscrowAccount.Address.Address(),
ResponderEscrow: remoteEscrowAccount.Address.Address(),
StartSequence: localEscrowAccount.SequenceNumber + 1,
Copy link
Contributor

Choose a reason for hiding this comment

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

💡

Suggested change
StartSequence: localEscrowAccount.SequenceNumber + 1,
StartSequence: ftx.SequenceNumber(),

require.NoError(t, err)
err = remoteChannel.IngestTx(ftxXDR, successResultXDR, resultMetaXDR)
require.NoError(t, err)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 The improvements to the tests look great. The fact that so many places in the tests needed updating for the state to be accurate, I think it would be worth adding into every test that was changed assertions on the result of calling channel.State(), that way these tests will ensure that the state value returned from the function is consistent over time. Wdyt?

@acharb
Copy link
Contributor Author

acharb commented Sep 1, 2021

note: moved the test improvements to this PR: #277

to use the channel.State for validations it looks like we'll need to add more state values, to ensure we're validating at a granular enough level. Closing this PR for now as that will take some additional thought and possible refactoring

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

Successfully merging this pull request may close these issues.

sdk/state: change open/confirm/payment validations to check against ChannelState() when possible
2 participants