-
Notifications
You must be signed in to change notification settings - Fork 9
sdk/state: refactor validations to check against the channel state #270
Changes from all commits
105eed0
f04f586
7da38ad
809433e
4c1636a
860dcf7
bcd9fc4
3e69491
9fd645b
1a33b01
b1e52be
687c88a
9665200
27032bb
25bb784
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -93,7 +93,11 @@ func (c *Channel) ProposeClose() (CloseAgreement, error) { | |
} | ||
|
||
// If the channel is not open yet, error. | ||
if c.latestAuthorizedCloseAgreement.isEmpty() { | ||
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") | ||
} | ||
|
||
|
@@ -122,9 +126,14 @@ func (c *Channel) ProposeClose() (CloseAgreement, error) { | |
|
||
func (c *Channel) validateClose(ca CloseAgreement) error { | ||
// If the channel is not open yet, error. | ||
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") | ||
} | ||
|
||
Comment on lines
-125
to
+136
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤔 The same thing here too. This guard on |
||
if ca.Details.IterationNumber != c.latestAuthorizedCloseAgreement.Details.IterationNumber { | ||
return fmt.Errorf("close agreement iteration number does not match saved latest authorized close agreement") | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,8 +4,10 @@ import ( | |
"testing" | ||
"time" | ||
|
||
"github.com/stellar/experimental-payment-channels/sdk/txbuildtest" | ||
"github.com/stellar/go/keypair" | ||
"github.com/stellar/go/network" | ||
"github.com/stellar/go/txnbuild" | ||
"github.com/stellar/go/xdr" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
@@ -107,16 +109,41 @@ func TestChannel_ProposeClose(t *testing.T) { | |
MaxOpenExpiry: 2 * time.Hour, | ||
}) | ||
|
||
open1, err := localChannel.ProposeOpen(OpenParams{ | ||
ObservationPeriodTime: 1, | ||
ObservationPeriodLedgerGap: 1, | ||
ExpiresAt: time.Now().Add(time.Hour), | ||
}) | ||
require.NoError(t, err) | ||
open2, err := remoteChannel.ConfirmOpen(open1) | ||
require.NoError(t, err) | ||
_, err = localChannel.ConfirmOpen(open2) | ||
require.NoError(t, err) | ||
// Put channel into the Open state. | ||
{ | ||
open1, err := localChannel.ProposeOpen(OpenParams{ | ||
ObservationPeriodTime: 1, | ||
ObservationPeriodLedgerGap: 1, | ||
ExpiresAt: time.Now().Add(time.Hour), | ||
}) | ||
require.NoError(t, err) | ||
open2, err := remoteChannel.ConfirmOpen(open1) | ||
require.NoError(t, err) | ||
_, err = localChannel.ConfirmOpen(open2) | ||
require.NoError(t, err) | ||
|
||
ftx, err := localChannel.OpenTx() | ||
require.NoError(t, err) | ||
ftxXDR, err := ftx.Base64() | ||
require.NoError(t, err) | ||
|
||
successResultXDR, err := txbuildtest.BuildResultXDR(true) | ||
require.NoError(t, err) | ||
resultMetaXDR, err := txbuildtest.BuildFormationResultMetaXDR(txbuildtest.FormationResultMetaParams{ | ||
InitiatorSigner: localSigner.Address(), | ||
ResponderSigner: remoteSigner.Address(), | ||
InitiatorEscrow: localEscrowAccount.Address.Address(), | ||
ResponderEscrow: remoteEscrowAccount.Address.Address(), | ||
StartSequence: localEscrowAccount.SequenceNumber + 1, | ||
Asset: txnbuild.NativeAsset{}, | ||
}) | ||
require.NoError(t, err) | ||
|
||
err = localChannel.IngestTx(ftxXDR, successResultXDR, resultMetaXDR) | ||
require.NoError(t, err) | ||
err = remoteChannel.IngestTx(ftxXDR, successResultXDR, resultMetaXDR) | ||
require.NoError(t, err) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
||
// If the local proposes a close, the agreement will have them as the proposer. | ||
closeByLocal, err := localChannel.ProposeClose() | ||
|
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -192,9 +192,14 @@ func (c *Channel) OpenTx() (formationTx *txnbuild.Transaction, err error) { | |||||||||
// initiating the channel. | ||||||||||
func (c *Channel) ProposeOpen(p OpenParams) (OpenAgreement, error) { | ||||||||||
// if the channel is already open, error. | ||||||||||
if c.openAgreement.isFull() { | ||||||||||
return OpenAgreement{}, fmt.Errorf("cannot propose a new open if channel is already opened") | ||||||||||
cs, err := c.State() | ||||||||||
if err != nil { | ||||||||||
return OpenAgreement{}, fmt.Errorf("getting channel state: %w", err) | ||||||||||
} | ||||||||||
if cs >= StateOpen { | ||||||||||
return OpenAgreement{}, fmt.Errorf("cannot propose a new open if channel has already opened") | ||||||||||
Comment on lines
+199
to
+200
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❗ I think we're missing catching
Suggested change
🤔 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 ❗ Note that there's actually a bug in this function in the original code. The original condition should have been For example:
❓ 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. |
||||||||||
} | ||||||||||
|
||||||||||
c.startingSequence = c.initiatorEscrowAccount().SequenceNumber + 1 | ||||||||||
|
||||||||||
d := OpenAgreementDetails{ | ||||||||||
|
@@ -224,7 +229,11 @@ func (c *Channel) ProposeOpen(p OpenParams) (OpenAgreement, error) { | |||||||||
|
||||||||||
func (c *Channel) validateOpen(m OpenAgreement) error { | ||||||||||
// if the channel is already open, error. | ||||||||||
if c.openAgreement.isFull() { | ||||||||||
cs, err := c.State() | ||||||||||
if err != nil { | ||||||||||
return fmt.Errorf("getting channel state: %w", err) | ||||||||||
} | ||||||||||
if cs >= StateOpen { | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is better than There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
❗ I think there could be cases where |
||||||||||
return fmt.Errorf("cannot confirm a new open if channel is already opened") | ||||||||||
} | ||||||||||
|
||||||||||
|
There was a problem hiding this comment.
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:
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?
There was a problem hiding this comment.
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