Skip to content
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

R4R: Implement HTLC claim and refund #1956

Merged
merged 8 commits into from
Sep 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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