Skip to content
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
1 change: 1 addition & 0 deletions api/handlers/signing.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const (
AcrossProtocol ProtocolType = "across"
MayanProtocol ProtocolType = "mayan"
RhinestoneProtocol ProtocolType = "rhinestone"
LifiProtocol ProtocolType = "lifi"
)

type SigningBody struct {
Expand Down
124 changes: 124 additions & 0 deletions api/handlers/unlock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package handlers

import (
"encoding/hex"
"encoding/json"
"fmt"
"math/big"
"net/http"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/gorilla/mux"
evmMessage "github.com/sprintertech/sprinter-signing/chains/evm/message"
"github.com/sprintertech/sprinter-signing/tss/ecdsa/signing"
"github.com/sygmaprotocol/sygma-core/relayer/message"
)

const SIGNATURE_TIMEOUT = time.Second * 15

type UnlockResponse struct {
Signature string `json:"signature"`
ID string `json:"id"`
}

type UnlockBody struct {
ChainId uint64
Protocol ProtocolType `json:"protocol"`
OrderID string `json:"orderId"`
Settler string `json:"settler"`
}

type UnlockHandler struct {
chains map[uint64]struct{}
msgChan chan []*message.Message
}

func NewUnlockHandler(msgChn chan []*message.Message, chains map[uint64]struct{}) *UnlockHandler {
return &UnlockHandler{
chains: chains,
msgChan: msgChn,
}
}

// HandleSigning sends a message to the across message handler and returns status code 202
// if the deposit has been accepted for the signing process
func (h *UnlockHandler) HandleUnlock(w http.ResponseWriter, r *http.Request) {
b := &UnlockBody{}
d := json.NewDecoder(r.Body)
err := d.Decode(b)
if err != nil {
JSONError(w, fmt.Errorf("invalid request body: %s", err), http.StatusBadRequest)
return
}

vars := mux.Vars(r)
err = h.validate(b, vars)
if err != nil {
JSONError(w, fmt.Errorf("invalid request body: %s", err), http.StatusBadRequest)
return
}

sigChn := make(chan interface{}, 1)
var m *message.Message
switch b.Protocol {
case LifiProtocol:
{
m = evmMessage.NewLifiUnlockMessage(0, b.ChainId, &evmMessage.LifiUnlockData{
Source: 0,
Destination: b.ChainId,
SigChn: sigChn,
OrderID: b.OrderID,
Settler: common.HexToAddress(b.Settler),
})
}
default:
JSONError(w, fmt.Errorf("invalid protocol %s", b.Protocol), http.StatusBadRequest)
return
}
h.msgChan <- []*message.Message{m}

for {
select {
case <-time.After(SIGNATURE_TIMEOUT):
JSONError(w, fmt.Errorf("timeout"), http.StatusInternalServerError)
return
case sig := <-sigChn:
{
sig, ok := sig.(signing.EcdsaSignature)
if !ok {
JSONError(w, fmt.Errorf("invalid signature"), http.StatusInternalServerError)
return
}

data, _ := json.Marshal(UnlockResponse{
Signature: hex.EncodeToString(sig.Signature),
ID: sig.ID,
})
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(data)
return
}
}
}
}

func (h *UnlockHandler) validate(b *UnlockBody, vars map[string]string) error {
chainId, ok := new(big.Int).SetString(vars["chainId"], 10)
if !ok {
return fmt.Errorf("field 'chainId' invalid")
}
b.ChainId = chainId.Uint64()

if b.ChainId == 0 {
return fmt.Errorf("missing field 'chainId'")
}

_, ok = h.chains[b.ChainId]
if !ok {
return fmt.Errorf("chain '%d' not supported", b.ChainId)
}

return nil
}
129 changes: 129 additions & 0 deletions api/handlers/unlock_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package handlers_test

import (
"bytes"
"encoding/hex"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"testing"

"github.com/gorilla/mux"
"github.com/sprintertech/sprinter-signing/api/handlers"
across "github.com/sprintertech/sprinter-signing/chains/evm/message"
"github.com/sprintertech/sprinter-signing/tss/ecdsa/signing"
"github.com/stretchr/testify/suite"
"github.com/sygmaprotocol/sygma-core/relayer/message"
)

type UnlockHandlerTestSuite struct {
suite.Suite

chains map[uint64]struct{}
}

func TestRunUnlockHandlerTestSuite(t *testing.T) {
suite.Run(t, new(UnlockHandlerTestSuite))
}

func (s *UnlockHandlerTestSuite) SetupTest() {
chains := make(map[uint64]struct{})
chains[1] = struct{}{}
s.chains = chains
}

func (s *UnlockHandlerTestSuite) Test_HandleUnlock_InvalidRequest() {
msgChn := make(chan []*message.Message)
handler := handlers.NewUnlockHandler(msgChn, s.chains)

input := handlers.UnlockBody{
Protocol: "lifi",
OrderID: "id",
Settler: "settler",
}
body, _ := json.Marshal(input)

req := httptest.NewRequest(http.MethodPost, "/v1/chains/1/unlocks", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")

recorder := httptest.NewRecorder()

go func() {
msg := <-msgChn
ad := msg[0].Data.(*across.LifiUnlockData)
ad.SigChn <- signing.EcdsaSignature{}
}()

handler.HandleUnlock(recorder, req)

s.Equal(http.StatusBadRequest, recorder.Code)
}

func (s *UnlockHandlerTestSuite) Test_HandleUnlock_InvalidProtocol() {
msgChn := make(chan []*message.Message)
handler := handlers.NewUnlockHandler(msgChn, s.chains)

input := handlers.UnlockBody{
Protocol: "across",
OrderID: "id",
Settler: "settler",
}
body, _ := json.Marshal(input)

req := httptest.NewRequest(http.MethodPost, "/v1/chains/1/unlocks", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req = mux.SetURLVars(req, map[string]string{
"chainId": "1",
})

recorder := httptest.NewRecorder()

go func() {
msg := <-msgChn
ad := msg[0].Data.(*across.LifiUnlockData)
ad.SigChn <- signing.EcdsaSignature{}
}()

handler.HandleUnlock(recorder, req)

s.Equal(http.StatusBadRequest, recorder.Code)
}

func (s *UnlockHandlerTestSuite) Test_HandleUnlock_ValidRequest() {
msgChn := make(chan []*message.Message)
handler := handlers.NewUnlockHandler(msgChn, s.chains)

input := handlers.UnlockBody{
Protocol: "lifi",
OrderID: "id",
Settler: "settler",
}
body, _ := json.Marshal(input)

req := httptest.NewRequest(http.MethodPost, "/v1/chains/1/unlocks", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req = mux.SetURLVars(req, map[string]string{
"chainId": "1",
})

recorder := httptest.NewRecorder()

sigBytes, _ := hex.DecodeString("abcd")
go func() {
msg := <-msgChn
ad := msg[0].Data.(*across.LifiUnlockData)
ad.SigChn <- signing.EcdsaSignature{
Signature: sigBytes,
ID: "id",
}
}()

handler.HandleUnlock(recorder, req)

s.Equal(http.StatusOK, recorder.Code)
data, err := io.ReadAll(recorder.Body)
s.Nil(err)

s.Equal(string(data), "{\"signature\":\"abcd\",\"id\":\"id\"}")
}
2 changes: 2 additions & 0 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ func Serve(
ctx context.Context,
addr string,
signingHandler *handlers.SigningHandler,
unlockHandler *handlers.UnlockHandler,
statusHandler *handlers.StatusHandler,
confirmationsHandler *handlers.ConfirmationsHandler,
) {
r := mux.NewRouter()
r.HandleFunc("/v1/chains/{chainId:[0-9]+}/unlocks", unlockHandler.HandleUnlock).Methods("POST")
r.HandleFunc("/v1/chains/{chainId:[0-9]+}/signatures", signingHandler.HandleSigning).Methods("POST")
r.HandleFunc("/v1/chains/{chainId:[0-9]+}/signatures/{depositId}", statusHandler.HandleRequest).Methods("GET")
r.HandleFunc("/v1/chains/{chainId:[0-9]+}/confirmations", confirmationsHandler.HandleRequest).Methods("GET")
Expand Down
19 changes: 18 additions & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,16 @@ func Run() error {
confirmationsPerChain[*c.GeneralChainConfig.Id] = c.ConfirmationsByValue
}

lifiUnlockMh := evmMessage.NewLifiUnlockHandler(
*c.GeneralChainConfig.Id,
repayerAddresses,
coordinator,
host,
communication,
keyshareStore,
)
mh.RegisterMessageHandler(evmMessage.LifiUnlockMessage, lifiUnlockMh)

var startBlock *big.Int
var listener *coreListener.EVMListener
eventHandlers := make([]coreListener.EventHandler, 0)
Expand Down Expand Up @@ -316,7 +326,14 @@ func Run() error {
signingHandler := handlers.NewSigningHandler(msgChan, supportedChains)
statusHandler := handlers.NewStatusHandler(signatureCache, supportedChains)
confirmationsHandler := handlers.NewConfirmationsHandler(confirmationsPerChain)
go api.Serve(ctx, configuration.RelayerConfig.ApiAddr, signingHandler, statusHandler, confirmationsHandler)
unlockHandler := handlers.NewUnlockHandler(msgChan, supportedChains)
go api.Serve(
ctx,
configuration.RelayerConfig.ApiAddr,
signingHandler,
unlockHandler,
statusHandler,
confirmationsHandler)

sig := <-sysErr
log.Info().Msgf("terminating got ` [%v] signal", sig)
Expand Down
26 changes: 24 additions & 2 deletions chains/evm/message/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import (
)

const (
AcrossMessage = "AcrossMessage"
MayanMessage = "MayanMessage"
AcrossMessage = "AcrossMessage"
MayanMessage = "MayanMessage"
LifiUnlockMessage = "LifiUnlockMessage"

DOMAIN_NAME = "LiquidityPool"
VERSION = "1.0.0"
Expand Down Expand Up @@ -95,6 +96,27 @@ func NewRhinestoneMessage(source, destination uint64, rhinestoneData *Rhinestone
}
}

type LifiUnlockData struct {
SigChn chan interface{} `json:"-"`

OrderID string
Settler common.Address

Coordinator peer.ID
Source uint64
Destination uint64
}

func NewLifiUnlockMessage(source, destination uint64, lifiData *LifiUnlockData) *message.Message {
return &message.Message{
Source: source,
Destination: destination,
Data: lifiData,
Type: LifiUnlockMessage,
Timestamp: time.Now(),
}
}

// unlockHash calculates the hash that has to signed and submitted on-chain to the liquidity
// pool contract.
func unlockHash(
Expand Down
Loading
Loading