-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #74 from filecoin-project/feat/payment-channels
implement initial payment channel actor
- Loading branch information
Showing
12 changed files
with
402 additions
and
101 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,281 @@ | ||
package actors | ||
|
||
import ( | ||
"bytes" | ||
|
||
"github.com/filecoin-project/go-lotus/chain/actors/aerrors" | ||
"github.com/filecoin-project/go-lotus/chain/address" | ||
"github.com/filecoin-project/go-lotus/chain/types" | ||
|
||
cbor "github.com/ipfs/go-ipld-cbor" | ||
) | ||
|
||
const ChannelClosingDelay = 6 * 60 * 2 // six hours | ||
|
||
func init() { | ||
cbor.RegisterCborType(PaymentChannelActorState{}) | ||
cbor.RegisterCborType(PCAConstructorParams{}) | ||
cbor.RegisterCborType(SignedVoucher{}) | ||
cbor.RegisterCborType(Merge{}) | ||
cbor.RegisterCborType(LaneState{}) | ||
cbor.RegisterCborType(UpdateChannelState{}) | ||
} | ||
|
||
type PaymentChannelActor struct{} | ||
|
||
type LaneState struct { | ||
Closed bool | ||
Redeemed types.BigInt | ||
Nonce uint64 | ||
} | ||
|
||
type PaymentChannelActorState struct { | ||
From address.Address | ||
To address.Address | ||
|
||
ChannelTotal types.BigInt | ||
ToSend types.BigInt | ||
|
||
ClosingAt uint64 | ||
MinCloseHeight uint64 | ||
|
||
LaneStates map[uint64]*LaneState | ||
|
||
VerifActor address.Address | ||
VerifMethod uint64 | ||
} | ||
|
||
func (pca PaymentChannelActor) Exports() []interface{} { | ||
return []interface{}{ | ||
0: pca.Constructor, | ||
1: pca.UpdateChannelState, | ||
2: pca.Close, | ||
3: pca.Collect, | ||
} | ||
} | ||
|
||
type PCAConstructorParams struct { | ||
To address.Address | ||
VerifActor address.Address | ||
VerifMethod uint64 | ||
} | ||
|
||
func (pca PaymentChannelActor) Constructor(act *types.Actor, vmctx types.VMContext, params *PCAConstructorParams) ([]byte, ActorError) { | ||
var self PaymentChannelActorState | ||
self.From = vmctx.Origin() | ||
self.To = params.To | ||
self.VerifActor = params.VerifActor | ||
self.VerifMethod = params.VerifMethod | ||
|
||
storage := vmctx.Storage() | ||
c, err := storage.Put(self) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if err := storage.Commit(EmptyCBOR, c); err != nil { | ||
return nil, err | ||
} | ||
|
||
return nil, nil | ||
} | ||
|
||
type SignedVoucher struct { | ||
TimeLock uint64 | ||
SecretPreimage []byte | ||
Extra []byte | ||
Lane uint64 | ||
Nonce uint64 | ||
Amount types.BigInt | ||
MinCloseHeight uint64 | ||
|
||
Merges []Merge | ||
|
||
Signature types.Signature | ||
} | ||
|
||
type Merge struct { | ||
Lane uint64 | ||
Nonce uint64 | ||
} | ||
|
||
type UpdateChannelState struct { | ||
Sv SignedVoucher | ||
Secret []byte | ||
Proof []byte | ||
} | ||
|
||
func hash(b []byte) []byte { | ||
panic("blake 2b hash pls") | ||
} | ||
|
||
func (pca PaymentChannelActor) UpdateChannelState(act *types.Actor, vmctx types.VMContext, params *UpdateChannelState) ([]byte, ActorError) { | ||
var self PaymentChannelActorState | ||
oldstate := vmctx.Storage().GetHead() | ||
storage := vmctx.Storage() | ||
if err := storage.Get(oldstate, &self); err != nil { | ||
return nil, err | ||
} | ||
|
||
sv := params.Sv | ||
|
||
if err := vmctx.VerifySignature(sv.Signature, self.From); err != nil { | ||
return nil, err | ||
} | ||
|
||
if vmctx.BlockHeight() < sv.TimeLock { | ||
return nil, aerrors.New(2, "cannot use this voucher yet!") | ||
} | ||
|
||
if sv.SecretPreimage != nil { | ||
if !bytes.Equal(hash(params.Secret), sv.SecretPreimage) { | ||
return nil, aerrors.New(3, "Incorrect secret!") | ||
} | ||
} | ||
|
||
if sv.Extra != nil { | ||
if self.VerifActor == address.Undef { | ||
return nil, aerrors.New(4, "no verifActor for extra data") | ||
} | ||
|
||
encoded, err := SerializeParams([]interface{}{sv.Extra, params.Proof}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
_, err = vmctx.Send(self.VerifActor, self.VerifMethod, types.NewInt(0), encoded) | ||
if err != nil { | ||
return nil, aerrors.New(5, "spend voucher verification failed") | ||
} | ||
} | ||
|
||
ls := self.LaneStates[sv.Lane] | ||
if ls.Closed { | ||
return nil, aerrors.New(6, "cannot redeem a voucher on a closed lane") | ||
} | ||
|
||
if ls.Nonce > sv.Nonce { | ||
return nil, aerrors.New(7, "voucher has an outdated nonce, cannot redeem") | ||
} | ||
|
||
mergeValue := types.NewInt(0) | ||
for _, merge := range sv.Merges { | ||
if merge.Lane == sv.Lane { | ||
return nil, aerrors.New(8, "voucher cannot merge its own lane") | ||
} | ||
|
||
ols := self.LaneStates[merge.Lane] | ||
|
||
if ols.Nonce >= merge.Nonce { | ||
return nil, aerrors.New(9, "merge in voucher has outdated nonce, cannot redeem") | ||
} | ||
|
||
mergeValue = types.BigAdd(mergeValue, ols.Redeemed) | ||
ols.Nonce = merge.Nonce | ||
} | ||
|
||
ls.Nonce = sv.Nonce | ||
balanceDelta := types.BigSub(sv.Amount, types.BigAdd(mergeValue, ls.Redeemed)) | ||
ls.Redeemed = sv.Amount | ||
|
||
newSendBalance := types.BigAdd(self.ToSend, balanceDelta) | ||
if types.BigCmp(newSendBalance, types.NewInt(0)) < 0 { | ||
// TODO: is this impossible? | ||
return nil, aerrors.New(10, "voucher would leave channel balance negative") | ||
} | ||
|
||
if types.BigCmp(newSendBalance, self.ChannelTotal) > 0 { | ||
return nil, aerrors.New(11, "not enough funds in channel to cover voucher") | ||
} | ||
|
||
self.ToSend = newSendBalance | ||
|
||
if sv.MinCloseHeight != 0 { | ||
if self.ClosingAt != 0 && self.ClosingAt < sv.MinCloseHeight { | ||
self.ClosingAt = sv.MinCloseHeight | ||
} | ||
if self.MinCloseHeight < sv.MinCloseHeight { | ||
self.MinCloseHeight = sv.MinCloseHeight | ||
} | ||
} | ||
|
||
ncid, err := storage.Put(self) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if err := storage.Commit(oldstate, ncid); err != nil { | ||
return nil, err | ||
} | ||
|
||
return nil, nil | ||
} | ||
|
||
func (pca PaymentChannelActor) Close(act *types.Actor, vmctx types.VMContext, params struct{}) ([]byte, aerrors.ActorError) { | ||
var self PaymentChannelActorState | ||
storage := vmctx.Storage() | ||
oldstate := storage.GetHead() | ||
if err := storage.Get(oldstate, &self); err != nil { | ||
return nil, err | ||
} | ||
|
||
if vmctx.Message().From != self.From && vmctx.Message().From != self.To { | ||
return nil, aerrors.New(1, "not authorized to close channel") | ||
} | ||
|
||
if self.ClosingAt != 0 { | ||
return nil, aerrors.New(2, "channel already closing") | ||
} | ||
|
||
self.ClosingAt = vmctx.BlockHeight() + ChannelClosingDelay | ||
if self.ClosingAt < self.MinCloseHeight { | ||
self.ClosingAt = self.MinCloseHeight | ||
} | ||
|
||
ncid, err := storage.Put(self) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if err := storage.Commit(oldstate, ncid); err != nil { | ||
return nil, err | ||
} | ||
|
||
return nil, nil | ||
} | ||
|
||
func (pca PaymentChannelActor) Collect(act *types.Actor, vmctx types.VMContext, params struct{}) ([]byte, aerrors.ActorError) { | ||
var self PaymentChannelActorState | ||
storage := vmctx.Storage() | ||
oldstate := storage.GetHead() | ||
if err := storage.Get(oldstate, &self); err != nil { | ||
return nil, err | ||
} | ||
|
||
if self.ClosingAt == 0 { | ||
return nil, aerrors.New(1, "payment channel not closing or closed") | ||
} | ||
|
||
if vmctx.BlockHeight() < self.ClosingAt { | ||
return nil, aerrors.New(2, "payment channel not closed yet") | ||
} | ||
_, err := vmctx.Send(self.From, 0, types.BigSub(self.ChannelTotal, self.ToSend), nil) | ||
if err != nil { | ||
return nil, err | ||
} | ||
_, err = vmctx.Send(self.To, 0, self.ToSend, nil) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
self.ChannelTotal = types.NewInt(0) | ||
self.ToSend = types.NewInt(0) | ||
|
||
ncid, err := storage.Put(self) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if err := storage.Commit(oldstate, ncid); err != nil { | ||
return nil, err | ||
} | ||
|
||
return nil, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.