Skip to content

Commit

Permalink
feat(funder): Introduce egoistic funding
Browse files Browse the repository at this point in the history
One of the participants can be egoistic and only deposits if the other(s) have deposited their amount
  • Loading branch information
cryptphil committed Jan 19, 2024
1 parent 5bbe33a commit 045c784
Showing 1 changed file with 105 additions and 4 deletions.
109 changes: 105 additions & 4 deletions channel/funder.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ type assetHolder struct {
type Funder struct {
mtx sync.RWMutex

EgoisticPart []bool

ContractBackend
// accounts associates an Account to every AssetIndex.
accounts map[AssetMapKey]accounts.Account
Expand All @@ -80,6 +82,14 @@ func NewFunder(backend ContractBackend) *Funder {
}
}

func (f *Funder) SetEgoisticPart(idx channel.Index, numParts int) {

Check warning on line 85 in channel/funder.go

View workflow job for this annotation

GitHub Actions / Lint

exported: exported method Funder.SetEgoisticPart should have comment or be unexported (revive)
f.mtx.Lock()
defer f.mtx.Unlock()

f.EgoisticPart = make([]bool, numParts)
f.EgoisticPart[idx] = true
}

// RegisterAsset registers the depositor and account for the specified asset in
// the funder.
//
Expand Down Expand Up @@ -210,6 +220,12 @@ func (f *Funder) fundAssets(ctx context.Context, assets []channel.Asset, channel
errg := perror.NewGatherer()
fundingIDs := FundingIDs(channelID, req.Params.Parts...)

// TODO: We need a check whether the length matches and whether only one idx is set to true.

Check failure on line 223 in channel/funder.go

View workflow job for this annotation

GitHub Actions / Lint

channel/funder.go:223: Line contains TODO/BUG/FIXME: "TODO: We need a check whether the length..." (godox)
egoistic := false
if len(f.EgoisticPart) != 0 {
egoistic = f.EgoisticPart[req.Idx]
}

for i, asset := range assets {
// Bind contract.
assetIdx, ok := assetIdx(req.State.Assets, asset)
Expand All @@ -218,10 +234,20 @@ func (f *Funder) fundAssets(ctx context.Context, assets []channel.Asset, channel
continue
}
contract := bindAssetHolder(f.ContractBackend, asset, assetIdx)
// Wait for the funding event.
errg.Go(func() error {
return f.waitForFundingConfirmation(ctx, req, contract, fundingIDs)
})

if egoistic {
err := f.EgoisticWaitForFundingConfirmation(ctx, req, contract, fundingIDs)
if err != nil {
f.log.WithField("asset", asset).WithError(err).Error("Could not fund asset")
errg.Add(errors.WithMessage(err, "funding asset"))
continue
}
} else {
// Wait for the funding event in a goroutine.
errg.Go(func() error {
return f.waitForFundingConfirmation(ctx, req, contract, fundingIDs)
})
}

// Send the funding TX.
tx, err := f.sendFundingTx(ctx, asset, req, contract, fundingIDs[req.Idx])
Expand Down Expand Up @@ -401,6 +427,81 @@ loop:
return nil
}

// EgoisticWaitForFundingConfirmation waits for the confirmation events on the blockchain that
// all peers except oneself (request.Idx) successfully funded the channel for the specified asset
// according to the funding agreement.
func (f *Funder) EgoisticWaitForFundingConfirmation(ctx context.Context, request channel.FundingReq, asset assetHolder, fundingIDs [][32]byte) error {

Check failure on line 433 in channel/funder.go

View workflow job for this annotation

GitHub Actions / Lint

Function 'EgoisticWaitForFundingConfirmation' is too long (69 > 60) (funlen)
// If asset on different ledger, return.
a := request.State.Assets[asset.assetIndex]
ethAsset, ok := a.(*Asset)
if !ok {
return fmt.Errorf("wrong type: expected *Asset, got %T", a)
}
if ethAsset.ChainID.MapKey() != f.chainID.MapKey() {
return nil
}

// Subscribe to events.
deposited, sub, subErr, err := f.subscribeDeposited(ctx, asset.contract, fundingIDs...)
if err != nil {
return errors.WithMessage(err, "subscribing to deposited event")
}
defer sub.Close()

remainingAll := request.Agreement.Clone()[asset.assetIndex]
// Create a new remainingOthers slice excluding the balance of the current participant.
remainingOthers := make([]*big.Int, 0, len(remainingAll)-1)
for i, bal := range remainingAll {
if channel.Index(i) != request.Idx {
remainingOthers = append(remainingOthers, bal)
}
}

// Calculate the total of the remainingOthers balances.
remainingTotal := channel.Balances([][]*big.Int{remainingOthers}).Sum()[0]
if remainingTotal.Cmp(big.NewInt(0)) <= 0 {
return nil
}
loop:
for {
select {
case rawEvent := <-deposited:
event, ok := rawEvent.Data.(*assetholder.AssetholderDeposited)
if !ok {
log.Panic("wrong event type")
}
log := f.log.WithField("fundingID", event.FundingID)

idx := partIdx(event.FundingID, fundingIDs)
// Ignore if the current participant should have deposited.
if channel.Index(idx) != request.Idx {
continue
}

remainingForPart := remainingOthers[idx]
remainingForPart.Sub(remainingForPart, event.Amount)
log.Debugf("peer[%d]: got: %v, remainingOthers for [%d, %d] = %v", request.Idx, event.Amount, asset.assetIndex, idx, remainingForPart)

// Exit loop if fully funded.
remainingTotal := channel.Balances([][]*big.Int{remainingOthers}).Sum()[0]
if remainingTotal.Cmp(big.NewInt(0)) <= 0 {
break loop
}
case <-ctx.Done():
return fundingTimeoutError(remainingOthers, asset)
case err := <-subErr:
// Resolve race between ctx and subErr, as ctx fires both events.
select {
case <-ctx.Done():
return fundingTimeoutError(remainingOthers, asset)
default:
}
return err
}
}
return nil
}

func fundingTimeoutError(remaining []channel.Bal, asset assetHolder) error {
var indices []channel.Index
for k, bals := range remaining {
Expand Down

0 comments on commit 045c784

Please sign in to comment.