Skip to content

Commit

Permalink
Merge pull request #1956 from irisnet/segue/htlc
Browse files Browse the repository at this point in the history
R4R: Implement HTLC claim and refund
  • Loading branch information
chengwenxi committed Sep 6, 2019
2 parents ff83f29 + ac81de5 commit 6d5b07d
Show file tree
Hide file tree
Showing 12 changed files with 413 additions and 12 deletions.
28 changes: 26 additions & 2 deletions app/v2/htlc/abci.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,36 @@
package htlc

import (
"encoding/hex"
"fmt"

"github.com/irisnet/irishub/app/v2/htlc/internal/types"
sdk "github.com/irisnet/irishub/types"
)

// EndBlocker handles block ending logic
func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) {
// TODO
func EndBlocker(ctx sdk.Context, k Keeper) (resTags sdk.Tags) {
// check htlc expire and set state from Open to Expired
ctx = ctx.WithLogger(ctx.Logger().With("handler", "EndBlock").With("module", "iris/htlc"))

currentBlockHeight := uint64(ctx.BlockHeight())
iterator := k.IterateHTLCExpireQueueByHeight(ctx, currentBlockHeight)
defer iterator.Close()

for ; iterator.Valid(); iterator.Next() {

secretHashLock := iterator.Key()
htlc, _ := k.GetHTLC(ctx, secretHashLock)

htlc.State = types.StateExpired
k.SetHTLC(ctx, htlc, secretHashLock)
k.DeleteHTLCFromExpireQueue(ctx, currentBlockHeight, secretHashLock)

ctx.Logger().Info(fmt.Sprintf("HTLC [%s] is expired", hex.EncodeToString(secretHashLock)))
}

// TODO: alternative
// check expire => refund => delete HTLC from expire queue

return nil
}
11 changes: 10 additions & 1 deletion app/v2/htlc/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import (
// exported types
type (
MsgCreateHTLC = types.MsgCreateHTLC
HTLC = types.HTLC
MsgClaimHTLC = types.MsgClaimHTLC
MsgRefundHTLC = types.MsgRefundHTLC

HTLC = types.HTLC

Params = types.Params
GenesisState = types.GenesisState
Expand All @@ -28,8 +31,14 @@ var (
RegisterCodec = types.RegisterCodec

NewMsgCreateHTLC = types.NewMsgCreateHTLC
NewMsgClaimHTLC = types.NewMsgClaimHTLC
NewMsgRefundHTLC = types.NewMsgRefundHTLC
NewHTLC = types.NewHTLC

StateOpen = types.StateOpen
StateCompleted = types.StateCompleted
StateExpired = types.StateExpired

ValidateSecretHashLock = types.ValidateSecretHashLock

QueryHTLC = types.QueryHTLC
Expand Down
35 changes: 32 additions & 3 deletions app/v2/htlc/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,21 @@ func NewHandler(k Keeper) sdk.Handler {
switch msg := msg.(type) {
case MsgCreateHTLC:
return handleMsgCreateHTLC(ctx, k, msg)
case MsgClaimHTLC:
return handleMsgClaimHTLC(ctx, k, msg)
case MsgRefundHTLC:
return handleMsgRefundHTLC(ctx, k, msg)
default:
return sdk.ErrTxDecode("invalid message parsed in HTLC module").Result()
}

return sdk.ErrTxDecode("invalid message parsed in HTLC module").Result()
}
}

// handleMsgCreateHTLC handles MsgCreateHTLC
func handleMsgCreateHTLC(ctx sdk.Context, k Keeper, msg MsgCreateHTLC) sdk.Result {
secret := make([]byte, 32)
expireHeight := msg.TimeLock + uint64(ctx.BlockHeight())
state := uint8(0)
state := StateOpen

htlc := NewHTLC(msg.Sender, msg.Receiver, msg.ReceiverOnOtherChain, msg.OutAmount, msg.InAmount, secret, msg.Timestamp, expireHeight, state)
secretHashLock, _ := hex.DecodeString(msg.SecretHashLock)
Expand All @@ -38,3 +40,30 @@ func handleMsgCreateHTLC(ctx sdk.Context, k Keeper, msg MsgCreateHTLC) sdk.Resul
Tags: tags,
}
}

// handleMsgClaimHTLC handles MsgClaimHTLC
func handleMsgClaimHTLC(ctx sdk.Context, k Keeper, msg MsgClaimHTLC) sdk.Result {
secret, _ := hex.DecodeString(msg.Secret)
secretHash, _ := hex.DecodeString(msg.SecretHashLock)
tags, err := k.ClaimHTLC(ctx, secret, secretHash)
if err != nil {
return err.Result()
}

return sdk.Result{
Tags: tags,
}
}

// handleMsgRefundHTLC handles MsgRefundHTLC
func handleMsgRefundHTLC(ctx sdk.Context, k Keeper, msg MsgRefundHTLC) sdk.Result {
secretHash, _ := hex.DecodeString(msg.SecretHashLock)
tags, err := k.RefundHTLC(ctx, secretHash)
if err != nil {
return err.Result()
}

return sdk.Result{
Tags: tags,
}
}
96 changes: 94 additions & 2 deletions app/v2/htlc/internal/keeper/keeper.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package keeper

import (
"bytes"
"encoding/hex"
"fmt"

Expand Down Expand Up @@ -55,22 +56,107 @@ func (k Keeper) CreateHTLC(ctx sdk.Context, htlc types.HTLC, secretHashLock []by
return nil, err
}

// add to coinflow
ctx.CoinFlowTags().AppendCoinFlowTag(ctx, htlc.Sender.String(), htlcAddr.String(), htlc.OutAmount.String(), sdk.CoinHTLCCreateFlow, "")

// set the htlc
k.SetHTLC(ctx, htlc, secretHashLock)

// add to the expiration queue
k.AddHTLCToExpireQueue(ctx, htlc.ExpireHeight, secretHashLock)

createTags := sdk.NewTags(
types.TagSender, []byte(htlc.Sender),
types.TagReceiver, []byte(htlc.Receiver),
types.TagSender, []byte(htlc.Sender.String()),
types.TagReceiver, []byte(htlc.Receiver.String()),
types.TagReceiverOnOtherChain, htlc.ReceiverOnOtherChain,
types.TagSecretHashLock, []byte(hex.EncodeToString(secretHashLock)),
)

return createTags, nil
}

func (k Keeper) ClaimHTLC(ctx sdk.Context, secret []byte, secretHashLock []byte) (sdk.Tags, sdk.Error) {

// get the htlc
htlc, err := k.GetHTLC(ctx, secretHashLock)
if err != nil {
return nil, err
}

// check if not open
if htlc.State != types.StateOpen {
return nil, types.ErrStateIsNotOpen(k.codespace, fmt.Sprintf("HTLC state is not Open."))
}

// check if secret not valid
if !bytes.Equal(k.GetSecretHashLock(secret, htlc.Timestamp), secretHashLock) {
return nil, types.ErrInvalidSecret(k.codespace, fmt.Sprintf("invalid secret: %s", hex.EncodeToString(secret)))
}

// do claim
htlcAddr := getHTLCAddress(htlc.OutAmount.Denom)
if _, err := k.bk.SendCoins(ctx, htlcAddr, htlc.Receiver, sdk.Coins{htlc.OutAmount}); err != nil {
return nil, err
}

// update secret and state in HTLC
htlc.Secret = secret
htlc.State = types.StateCompleted
k.SetHTLC(ctx, htlc, secretHashLock)
k.DeleteHTLCFromExpireQueue(ctx, uint64(ctx.BlockHeight()), secretHashLock)

// add to coinflow
ctx.CoinFlowTags().AppendCoinFlowTag(ctx, htlcAddr.String(), htlc.Receiver.String(), htlc.OutAmount.String(), sdk.CoinHTLCClaimFlow, "")

calimTags := sdk.NewTags(
types.TagSender, []byte(htlc.Sender.String()),
types.TagReceiver, []byte(htlc.Receiver.String()),
types.TagSecretHashLock, []byte(hex.EncodeToString(secretHashLock)),
types.TagSecret, []byte(hex.EncodeToString(secret)),
)

return calimTags, nil
}

func (k Keeper) RefundHTLC(ctx sdk.Context, secretHashLock []byte) (sdk.Tags, sdk.Error) {

// get the htlc
htlc, err := k.GetHTLC(ctx, secretHashLock)
if err != nil {
return nil, err
}

// check if not expired
if htlc.State != types.StateExpired {
return nil, types.ErrStateIsNotOpen(k.codespace, fmt.Sprintf("HTLC state is not Expired."))
}

// do refund
htlcAddr := getHTLCAddress(htlc.OutAmount.Denom)
if _, err := k.bk.SendCoins(ctx, htlcAddr, htlc.Sender, sdk.Coins{htlc.OutAmount}); err != nil {
return nil, err
}

// update state in HTLC
htlc.State = types.StateRefunded
k.SetHTLC(ctx, htlc, secretHashLock)

// add to coinflow
ctx.CoinFlowTags().AppendCoinFlowTag(ctx, htlcAddr.String(), htlc.Sender.String(), htlc.OutAmount.String(), sdk.CoinHTLCRefundFlow, "")

refundTags := sdk.NewTags(
types.TagSender, []byte(htlc.Sender.String()),
types.TagSecretHashLock, []byte(hex.EncodeToString(secretHashLock)),
)

return refundTags, nil
}

// GetSecretHashLock calculates the secret hash lock
func (k Keeper) GetSecretHashLock(secret []byte, timestamp uint64) []byte {
return sdk.SHA256(append(secret, sdk.Uint64ToBigEndian(timestamp)...))
}

func (k Keeper) HasSecretHashLock(ctx sdk.Context, secretHashLock []byte) bool {
store := ctx.KVStore(k.storeKey)
return store.Has(KeyHTLC(secretHashLock))
Expand Down Expand Up @@ -119,3 +205,9 @@ func (k Keeper) DeleteHTLCFromExpireQueue(ctx sdk.Context, expireHeight uint64,
func getHTLCAddress(denom string) sdk.AccAddress {
return sdk.AccAddress(crypto.AddressHash([]byte(denom)))
}

// IterateHTLCExpireQueueByHeight iterates the HTLC expire queue by the specified height
func (k Keeper) IterateHTLCExpireQueueByHeight(ctx sdk.Context, height uint64) sdk.Iterator {
store := ctx.KVStore(k.storeKey)
return sdk.KVStorePrefixIterator(store, KeyHTLCExpireQueueSubspace(height))
}
5 changes: 5 additions & 0 deletions app/v2/htlc/internal/keeper/keeper_keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,8 @@ func KeyHTLC(secretHashLock []byte) []byte {
func KeyHTLCExpireQueue(expireHeight uint64, secretHashLock []byte) []byte {
return append(append(PrefixHTLCExpireQueue, sdk.Uint64ToBigEndian(expireHeight)...), secretHashLock...)
}

// KeyHTLCExpireQueueSubspace returns the key prefix for HTLC expiration queue
func KeyHTLCExpireQueueSubspace(expireHeight uint64) []byte {
return append(PrefixHTLCExpireQueue, sdk.Uint64ToBigEndian(expireHeight)...)
}
10 changes: 9 additions & 1 deletion app/v2/htlc/internal/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,13 @@ func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey) {
}

func TestKeeper_CreateHTLC(t *testing.T) {
// TODO
// TODO:
}

func TestKeeper_ClaimHTLC(t *testing.T) {
// TODO:
}

func TestKeeper_RefundHTLC(t *testing.T) {
// TODO:
}
15 changes: 15 additions & 0 deletions app/v2/htlc/internal/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ const (
CodeInvalidSecretHashLock sdk.CodeType = 102
CodeSecretHashLockAlreadyExists sdk.CodeType = 103
CodeInvalidTimeLock sdk.CodeType = 104
CodeInvalidSecret sdk.CodeType = 105
CodeStateIsNotOpen sdk.CodeType = 106
CodeStateIsNotExpired sdk.CodeType = 107
)

//----------------------------------------
Expand All @@ -38,3 +41,15 @@ func ErrSecretHashLockAlreadyExists(codespace sdk.CodespaceType, msg string) sdk
func ErrInvalidTimeLock(codespace sdk.CodespaceType, msg string) sdk.Error {
return sdk.NewError(codespace, CodeInvalidTimeLock, msg)
}

func ErrInvalidSecret(codespace sdk.CodespaceType, msg string) sdk.Error {
return sdk.NewError(codespace, CodeInvalidSecret, msg)
}

func ErrStateIsNotOpen(codespace sdk.CodespaceType, msg string) sdk.Error {
return sdk.NewError(codespace, CodeStateIsNotOpen, msg)
}

func ErrStateIsNotExpired(codespace sdk.CodespaceType, msg string) sdk.Error {
return sdk.NewError(codespace, CodeStateIsNotExpired, msg)
}
31 changes: 29 additions & 2 deletions app/v2/htlc/internal/types/htlc.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ import (
sdk "github.com/irisnet/irishub/types"
)

// the state of the HTLC
const (
StateOpen = uint8(0) // can claim
StateCompleted = uint8(1) // claimed
StateExpired = uint8(2) // Expired
StateRefunded = uint8(3) // Refunded
)

// HTLC represents a HTLC
type HTLC struct {
Sender sdk.AccAddress `json:"sender"` // the initiator address
Expand All @@ -21,7 +29,17 @@ type HTLC struct {
}

// NewHTLC constructs a HTLC
func NewHTLC(sender sdk.AccAddress, receiver sdk.AccAddress, receiverOnOtherChain []byte, outAmount sdk.Coin, inAmount uint64, secret []byte, timestamp uint64, expireHeight uint64, state uint8) HTLC {
func NewHTLC(
sender sdk.AccAddress,
receiver sdk.AccAddress,
receiverOnOtherChain []byte,
outAmount sdk.Coin,
inAmount uint64,
secret []byte,
timestamp uint64,
expireHeight uint64,
state uint8,
) HTLC {
return HTLC{
Sender: sender,
Receiver: receiver,
Expand Down Expand Up @@ -52,5 +70,14 @@ func (h HTLC) String() string {
Timestamp: %d
ExpireHeight: %d
State: %d`,
h.Sender, h.Receiver, h.ReceiverOnOtherChain, h.OutAmount.String(), h.InAmount, hex.EncodeToString(h.Secret), h.Timestamp, h.ExpireHeight, h.State)
h.Sender,
h.Receiver,
h.ReceiverOnOtherChain,
h.OutAmount.String(),
h.InAmount,
hex.EncodeToString(h.Secret),
h.Timestamp,
h.ExpireHeight,
h.State,
)
}
Loading

0 comments on commit 6d5b07d

Please sign in to comment.