-
Notifications
You must be signed in to change notification settings - Fork 19
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
Enforce fair outcome in direct and virtual fund objectives #1610
Changes from all commits
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 |
---|---|---|
|
@@ -110,6 +110,19 @@ func ChannelsExistWithCounterparty(counterparty types.Address, getChannels GetCh | |
return ok, nil | ||
} | ||
|
||
// hasEqualOutcome returns true if the outcome allocates an equal amount to each participant | ||
func hasEqualOutcome(s state.State, me types.Address) bool { | ||
for _, e := range s.Outcome { | ||
forMe := e.TotalAllocatedFor(types.AddressToDestination(me)) | ||
for _, a := range e.Allocations { | ||
if a.Amount.Cmp(forMe) != 0 { | ||
return false | ||
} | ||
} | ||
} | ||
return true | ||
} | ||
|
||
// ConstructFromPayload initiates a Objective with data calculated from | ||
// the supplied initialState and client address | ||
func ConstructFromPayload( | ||
|
@@ -124,6 +137,7 @@ func ConstructFromPayload( | |
return Objective{}, fmt.Errorf("could not get signed state payload: %w", err) | ||
} | ||
initialState := initialSignedState.State() | ||
|
||
err = initialState.FixedPart().Validate() | ||
if err != nil { | ||
return Objective{}, err | ||
|
@@ -135,6 +149,9 @@ func ConstructFromPayload( | |
return Objective{}, errors.New("attempted to initiate new direct-funding objective with IsFinal == true") | ||
} | ||
|
||
if !hasEqualOutcome(initialState, myAddress) { | ||
return Objective{}, errors.New("attempted to initiate new direct-funding objective with non-equal outcome") | ||
} | ||
Comment on lines
+152
to
+154
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. Follow on changes here if you like "uniform outcome". |
||
init := Objective{} | ||
|
||
if preApprove { | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -59,7 +59,7 @@ func TestNew(t *testing.T) { | |||||
request := NewObjectiveRequest( | ||||||
testState.Participants[1], | ||||||
testState.ChallengeDuration, | ||||||
testState.Outcome, | ||||||
testState.Outcome.Clone(), | ||||||
0, | ||||||
testState.AppDefinition, | ||||||
) | ||||||
|
@@ -69,7 +69,7 @@ func TestNew(t *testing.T) { | |||||
} | ||||||
|
||||||
getByParticipantHasChannel := func(id types.Address) ([]*channel.Channel, error) { | ||||||
c, _ := channel.New(testState, 0) | ||||||
c, _ := channel.New(testState.Clone(), 0) | ||||||
return []*channel.Channel{c}, nil | ||||||
} | ||||||
|
||||||
|
@@ -83,10 +83,15 @@ func TestNew(t *testing.T) { | |||||
if _, err := NewObjective(request, false, testState.Participants[0], big.NewInt(TEST_CHAIN_ID), getByParticipant, getByConsensusHasChannel); err == nil { | ||||||
t.Errorf("Expected an error when constructing with an objective when an existing channel consensus channel exists") | ||||||
} | ||||||
|
||||||
request.Outcome[0].Allocations[0].Amount = big.NewInt(10) | ||||||
if _, err := NewObjective(request, false, testState.Participants[0], big.NewInt(TEST_CHAIN_ID), getByParticipant, getByConsensus); err == nil { | ||||||
t.Errorf("Expected an error when constructing a direct fund objective with an unfair outcome") | ||||||
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.
Suggested change
|
||||||
} | ||||||
} | ||||||
|
||||||
func TestConstructFromPayload(t *testing.T) { | ||||||
ss := state.NewSignedState(testState) | ||||||
ss := state.NewSignedState(testState.Clone()) | ||||||
id := protocols.ObjectiveId(ObjectivePrefix + testState.ChannelId().String()) | ||||||
op, err := protocols.CreateObjectivePayload(id, SignedStatePayload, ss) | ||||||
testhelpers.Ok(t, err) | ||||||
|
@@ -113,7 +118,7 @@ func TestConstructFromPayload(t *testing.T) { | |||||
|
||||||
func TestUpdate(t *testing.T) { | ||||||
id := protocols.ObjectiveId(ObjectivePrefix + testState.ChannelId().String()) | ||||||
op, err := protocols.CreateObjectivePayload(id, SignedStatePayload, state.NewSignedState(testState)) | ||||||
op, err := protocols.CreateObjectivePayload(id, SignedStatePayload, state.NewSignedState(testState.Clone())) | ||||||
testhelpers.Ok(t, err) | ||||||
// Construct various variables for use in TestUpdate | ||||||
s, _ := ConstructFromPayload(false, op, testState.Participants[0]) | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,16 +10,16 @@ import ( | |
) | ||
|
||
// prepareConsensusChannel prepares a consensus channel with a consensus outcome | ||
// - allocating 6 to left participant | ||
// - allocating 4 to right participant | ||
// - allocating 5 to left participant | ||
// - allocating 5 to right participant | ||
// - including the given guarantees | ||
func prepareConsensusChannel(role uint, leader, follower, leftActor testactors.Actor, guarantees ...consensus_channel.Guarantee) *consensus_channel.ConsensusChannel { | ||
return prepareConsensusChannelHelper(role, leader, follower, leftActor, 6, 4, 1, guarantees...) | ||
return prepareConsensusChannelHelper(role, leader, follower, leftActor, 5, 5, 1, guarantees...) | ||
} | ||
|
||
// consensusStateSignatures prepares a consensus channel with a consensus outcome and returns the signatures on the consensus state | ||
func consensusStateSignatures(leader, follower testactors.Actor, guarantees ...consensus_channel.Guarantee) [2]state.Signature { | ||
return prepareConsensusChannelHelper(0, leader, follower, leader, 0, 0, 2, guarantees...).Signatures() | ||
return prepareConsensusChannelHelper(0, leader, follower, leader, 0, 5, 2, guarantees...).Signatures() | ||
} | ||
|
||
func prepareConsensusChannelHelper(role uint, leader, follower, leftActor testactors.Actor, leftBalance, rightBalance, turnNum int, guarantees ...consensus_channel.Guarantee) *consensus_channel.ConsensusChannel { | ||
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. Out of scope nit: I wonder if there's a more useful way to name this function. "Helper" doesn't really tell us anything... 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. Out of scope but I think the virtual fund tests could use some refactoring in general. Helpers within helpers relying of specific test data made updating this code slightly tricky. |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ import ( | |
"github.com/statechannels/go-nitro/channel/consensus_channel" | ||
"github.com/statechannels/go-nitro/channel/state" | ||
"github.com/statechannels/go-nitro/channel/state/outcome" | ||
"github.com/statechannels/go-nitro/payments" | ||
"github.com/statechannels/go-nitro/protocols" | ||
"github.com/statechannels/go-nitro/types" | ||
) | ||
|
@@ -132,6 +133,21 @@ type Objective struct { | |
b0 types.Funds // Initial balance for Bob | ||
} | ||
|
||
// onlyPayerHasFunds checks that the outcome only has funds allocated to the payer | ||
func onlyPayerHasFunds(s state.State) bool { | ||
for _, e := range s.Outcome { | ||
total := e.TotalAllocated() | ||
for i, a := range e.Allocations { | ||
if i == payments.PAYER_INDEX && a.Amount.Cmp(total) != 0 { | ||
return false | ||
} else if i != payments.PAYER_INDEX && a.Amount.Cmp(big.NewInt(0)) != 0 { | ||
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. Super nit but you don't need the |
||
return false | ||
} | ||
} | ||
} | ||
return true | ||
} | ||
|
||
// NewObjective creates a new virtual funding objective from a given request. | ||
func NewObjective(request ObjectiveRequest, preApprove bool, myAddress types.Address, chainId *big.Int, getTwoPartyConsensusLedger GetTwoPartyConsensusLedgerFunction) (Objective, error) { | ||
var rightCC *consensus_channel.ConsensusChannel | ||
|
@@ -185,6 +201,10 @@ func constructFromState( | |
init.Status = protocols.Unapproved | ||
} | ||
|
||
if !onlyPayerHasFunds(initialStateOfV) { | ||
return Objective{}, errors.New("initial state of V has funds allocated to non-payer") | ||
} | ||
|
||
// Infer MyRole | ||
found := false | ||
for i, addr := range initialStateOfV.Participants { | ||
|
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.
hasUniformOutcome
is better, I think?