go-nitro icon indicating copy to clipboard operation
go-nitro copied to clipboard

Create a `channel-top-up` protocol

Open NiloCK opened this issue 2 years ago • 0 comments

There is currently no way for running ledger channels to be topped up. Topups are desirable because they are generally cheaper than finalizing a channel, disbursing its assets, and opening a new channel via a L1 deposit.

Protocol at a glance:

  • with
    • ledger channel L(X, Y) between participants X and Y.
    • X as the topper-upper (ie, the person who executes the L1 deposit to add funds)
    • top-up amount t
  1. take the current channel outcome and append X: t as the final outcome item
    • the channel outcome now probably resembles { X: x, Y: y, [someGuarantees]..., X: t}
    • exchange and co-sign this updated state
  2. await the Deposited(L(X,Y).channelID, t) event
  3. fold the X: t outcome item back into the previously existing X outcome item
    • result: { X: x+t, Y: y, [someGuarantees}... }
    • exchange and co-sign this updated state

Implementation Details

An ideal implementation of this protocol would leave the ledger channel usable for regular operations while the top-up is being run. Waiting on an on-chain event necessarily makes this a slow operation, but ledger channels are depended on for the faster, off-chain virtual fund and defund operations.

the consensus_channel implementation of ledger channels has customized representation of ledger outcomes:

type LedgerOutcome struct {
	assetAddress types.Address // Address of the asset type
	leader       Balance       // Balance of participants[0]
	follower     Balance       // Balance of participants[1]
	guarantees   map[types.Destination]Guarantee
	// add this?
        topUp        Balance // Balance of a running topup - usually empty
}

and conversions to and from canonical Exit-Format representation:

// AsOutcome converts a LedgerOutcome to an on-chain exit according to the following convention:
//   - the "leader" balance is first
//   - the "follower" balance is second
//   - guarantees follow, sorted according to their target destinations
func (o *LedgerOutcome) AsOutcome() outcome.Exit {
	// The first items are [leader, follower] balances
	allocations := outcome.Allocations{o.leader.AsAllocation(), o.follower.AsAllocation()}

	// Followed by guarantees, _sorted by the target destination_
	keys := make([]types.Destination, 0, len(o.guarantees))
	for k := range o.guarantees {
		keys = append(keys, k)
	}
	sort.Slice(keys, func(i, j int) bool {
		return keys[i].String() < keys[j].String()
	})

	for _, target := range keys {
		allocations = append(allocations, o.guarantees[target].AsAllocation())
	}

                // add this?
	        // the last item is the running topUp balance
	        if (o.topUp != Balance{}) {
		        allocations = append(allocations, o.topUp.AsAllocation())
	        }

	return outcome.Exit{
		outcome.SingleAssetExit{
			Asset:       o.assetAddress,
			Allocations: allocations,
		},
	}
}
// FromExit creates a new LedgerOutcome from the given SingleAssetExit.
//
// It makes the following assumptions about the exit:
//   - The first allocation entry is for the ledger leader
//   - The second allocation entry is for the ledger follower
//   - The last allocation may be a running topup               (added)
//   - All other allocations are guarantees
func FromExit(sae outcome.SingleAssetExit) (LedgerOutcome, error) {
	var (
		leader     = Balance{destination: sae.Allocations[0].Destination, amount: sae.Allocations[0].Amount}
		follower   = Balance{destination: sae.Allocations[1].Destination, amount: sae.Allocations[1].Amount}
		guarantees = make(map[types.Destination]Guarantee)
	)

	for _, a := range sae.Allocations {
		if a.AllocationType == outcome.GuaranteeAllocationType {
			gM, err := outcome.DecodeIntoGuaranteeMetadata(a.Metadata)
			if err != nil {
				return LedgerOutcome{}, fmt.Errorf("failed to decode guarantee metadata: %w", err)
			}

			g := Guarantee{
				amount: a.Amount,
				target: a.Destination,
				left:   gM.Left,
				right:  gM.Right,
			}
			guarantees[a.Destination] = g
		}
	}

	// add
        if len(guarantees) != len(sae.Allocations)-2 {
		last := sae.Allocations[len(sae.Allocations)-1]
		var topUp = Balance{destination: last.Destination, amount: last.Amount}
	}

	return LedgerOutcome{leader: leader, follower: follower, guarantees: guarantees, assetAddress: sae.Asset}, nil
}

NiloCK avatar Sep 07 '23 15:09 NiloCK