Skip to content

Commit f7424d4

Browse files
authored
feat: wait for confirmations based on value (#23)
* Implement token and confirmations config * Implement coinmarketcap price api * Add blocktime to config * Add coinmarketcap config * Separate chain block confirmations and confirmations by value * Add coinmarketcap cache * Update across tests with token configs * Implement confirmations handler * Remove hardcoded wait * Add confirmations route * Lint * Add coinmarket cap response timeout
1 parent 46e97f8 commit f7424d4

File tree

17 files changed

+799
-117
lines changed

17 files changed

+799
-117
lines changed

api/handlers/confirmations.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package handlers
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"math/big"
7+
"net/http"
8+
9+
"github.com/gorilla/mux"
10+
)
11+
12+
type ConfirmationsHandler struct {
13+
confirmationsByChain map[uint64]map[uint64]uint64
14+
}
15+
16+
func NewConfirmationsHandler(confirmationsByChain map[uint64]map[uint64]uint64) *ConfirmationsHandler {
17+
return &ConfirmationsHandler{
18+
confirmationsByChain: confirmationsByChain,
19+
}
20+
}
21+
22+
// HandleRequest returns confirmations by value buckets for the requested chain
23+
func (h *ConfirmationsHandler) HandleRequest(w http.ResponseWriter, r *http.Request) {
24+
vars := mux.Vars(r)
25+
chainId, ok := new(big.Int).SetString(vars["chainId"], 10)
26+
if !ok {
27+
JSONError(w, fmt.Errorf("invalid chainId"), http.StatusBadRequest)
28+
return
29+
}
30+
31+
confirmations, ok := h.confirmationsByChain[chainId.Uint64()]
32+
if !ok {
33+
JSONError(w, fmt.Errorf("no confirmations for chainID: %d", chainId.Uint64()), http.StatusNotFound)
34+
return
35+
}
36+
37+
data, _ := json.Marshal(confirmations)
38+
w.Header().Set("Content-Type", "application/json")
39+
w.WriteHeader(http.StatusOK)
40+
_, _ = w.Write(data)
41+
}

api/handlers/confirmations_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package handlers_test
2+
3+
import (
4+
"io"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
9+
"github.com/gorilla/mux"
10+
"github.com/sprintertech/sprinter-signing/api/handlers"
11+
"github.com/stretchr/testify/suite"
12+
)
13+
14+
type ConfirmationsHandlerTestSuite struct {
15+
suite.Suite
16+
}
17+
18+
func TestRunConfirmationsHandlerTestSuite(t *testing.T) {
19+
suite.Run(t, new(ConfirmationsHandlerTestSuite))
20+
}
21+
22+
func (s *ConfirmationsHandlerTestSuite) Test_HandleRequest_InvalidChainID() {
23+
handler := handlers.NewConfirmationsHandler(map[uint64]map[uint64]uint64{})
24+
25+
req := httptest.NewRequest(http.MethodPost, "/v1/chains/1/confirmations", nil)
26+
req = mux.SetURLVars(req, map[string]string{
27+
"chainId": "invalid",
28+
})
29+
30+
recorder := httptest.NewRecorder()
31+
32+
handler.HandleRequest(recorder, req)
33+
34+
s.Equal(http.StatusBadRequest, recorder.Code)
35+
}
36+
37+
func (s *ConfirmationsHandlerTestSuite) Test_HandleRequest_ChainNotFound() {
38+
handler := handlers.NewConfirmationsHandler(map[uint64]map[uint64]uint64{})
39+
40+
req := httptest.NewRequest(http.MethodPost, "/v1/chains/1/confirmations", nil)
41+
req = mux.SetURLVars(req, map[string]string{
42+
"chainId": "1",
43+
})
44+
45+
recorder := httptest.NewRecorder()
46+
47+
handler.HandleRequest(recorder, req)
48+
49+
s.Equal(http.StatusNotFound, recorder.Code)
50+
}
51+
52+
func (s *ConfirmationsHandlerTestSuite) Test_HandleRequest_ValidConfirmations() {
53+
expectedConfirmations := map[uint64]uint64{
54+
1000: 0,
55+
5000: 2,
56+
10000: 3,
57+
}
58+
59+
confirmations := make(map[uint64]map[uint64]uint64)
60+
confirmations[1] = expectedConfirmations
61+
handler := handlers.NewConfirmationsHandler(confirmations)
62+
63+
req := httptest.NewRequest(http.MethodPost, "/v1/chains/1/confirmations", nil)
64+
req = mux.SetURLVars(req, map[string]string{
65+
"chainId": "1",
66+
})
67+
68+
recorder := httptest.NewRecorder()
69+
70+
handler.HandleRequest(recorder, req)
71+
72+
s.Equal(http.StatusOK, recorder.Code)
73+
74+
data, err := io.ReadAll(recorder.Body)
75+
s.Nil(err)
76+
77+
s.Equal(string(data), "{\"1000\":0,\"10000\":3,\"5000\":2}")
78+
}

api/server.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ func Serve(
1515
addr string,
1616
signingHandler *handlers.SigningHandler,
1717
statusHandler *handlers.StatusHandler,
18+
confirmationsHandler *handlers.ConfirmationsHandler,
1819
) {
1920
r := mux.NewRouter()
2021
r.HandleFunc("/v1/chains/{chainId:[0-9]+}/signatures", signingHandler.HandleSigning).Methods("POST")
2122
r.HandleFunc("/v1/chains/{chainId:[0-9]+}/signatures/{depositId}", statusHandler.HandleRequest).Methods("GET")
23+
r.HandleFunc("/v1/chains/{chainId:[0-9]+}/confirmations", confirmationsHandler.HandleRequest).Methods("GET")
2224

2325
server := &http.Server{
2426
Addr: addr,

app/app.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"os/signal"
1313
"strings"
1414
"syscall"
15+
"time"
1516

1617
"github.com/ethereum/go-ethereum/common"
1718
"github.com/libp2p/go-libp2p/core/crypto"
@@ -31,6 +32,7 @@ import (
3132
"github.com/sprintertech/sprinter-signing/jobs"
3233
"github.com/sprintertech/sprinter-signing/keyshare"
3334
"github.com/sprintertech/sprinter-signing/metrics"
35+
"github.com/sprintertech/sprinter-signing/price"
3436
"github.com/sprintertech/sprinter-signing/topology"
3537
"github.com/sprintertech/sprinter-signing/tss"
3638
coreEvm "github.com/sygmaprotocol/sygma-core/chains/evm"
@@ -126,10 +128,15 @@ func Run() error {
126128
msgChan := make(chan []*message.Message)
127129
sigChn := make(chan interface{})
128130

131+
priceAPI := price.NewCoinmarketcapAPI(
132+
configuration.RelayerConfig.CoinmarketcapConfig.Url,
133+
configuration.RelayerConfig.CoinmarketcapConfig.ApiKey)
134+
129135
signatureCache := cache.NewSignatureCache(communication)
130136
go signatureCache.Watch(ctx, sigChn)
131137

132138
supportedChains := make(map[uint64]struct{})
139+
confirmationsPerChain := make(map[uint64]map[uint64]uint64)
133140
domains := make(map[uint64]relayer.RelayedChain)
134141
for _, chainConfig := range configuration.ChainConfigs {
135142
switch chainConfig["type"] {
@@ -148,11 +155,25 @@ func Run() error {
148155
mh := message.NewMessageHandler()
149156
if config.AcrossPool != "" {
150157
poolAddress := common.HexToAddress(config.AcrossPool)
151-
acrossMh := evmMessage.NewAcrossMessageHandler(client, poolAddress, coordinator, host, communication, keyshareStore, sigChn)
158+
acrossMh := evmMessage.NewAcrossMessageHandler(
159+
*config.GeneralChainConfig.Id,
160+
client,
161+
poolAddress,
162+
coordinator,
163+
host,
164+
communication,
165+
keyshareStore,
166+
priceAPI,
167+
sigChn,
168+
config.Tokens,
169+
config.ConfirmationsByValue,
170+
// nolint:gosec
171+
time.Duration(config.GeneralChainConfig.Blocktime)*time.Second)
152172
go acrossMh.Listen(ctx)
153173

154174
mh.RegisterMessageHandler(evmMessage.AcrossMessage, acrossMh)
155175
supportedChains[*config.GeneralChainConfig.Id] = struct{}{}
176+
confirmationsPerChain[*config.GeneralChainConfig.Id] = config.ConfirmationsByValue
156177
}
157178

158179
var startBlock *big.Int
@@ -168,7 +189,7 @@ func Run() error {
168189
adminAddress := common.HexToAddress(config.Admin)
169190
eventHandlers = append(eventHandlers, evmListener.NewKeygenEventHandler(l, tssListener, coordinator, host, communication, keyshareStore, adminAddress, networkTopology.Threshold))
170191
eventHandlers = append(eventHandlers, evmListener.NewRefreshEventHandler(l, topologyProvider, topologyStore, tssListener, coordinator, host, communication, connectionGate, keyshareStore, adminAddress))
171-
listener = coreListener.NewEVMListener(client, eventHandlers, blockstore, sygmaMetrics, *config.GeneralChainConfig.Id, config.BlockRetryInterval, config.BlockConfirmations, config.BlockInterval)
192+
listener = coreListener.NewEVMListener(client, eventHandlers, blockstore, sygmaMetrics, *config.GeneralChainConfig.Id, config.BlockRetryInterval, new(big.Int).SetUint64(config.GeneralChainConfig.BlockConfirmations), config.BlockInterval)
172193
}
173194

174195
chain := coreEvm.NewEVMChain(listener, mh, nil, *config.GeneralChainConfig.Id, startBlock)
@@ -201,7 +222,8 @@ func Run() error {
201222

202223
signingHandler := handlers.NewSigningHandler(msgChan, supportedChains)
203224
statusHandler := handlers.NewStatusHandler(signatureCache, supportedChains)
204-
go api.Serve(ctx, configuration.RelayerConfig.ApiAddr, signingHandler, statusHandler)
225+
confirmationsHandler := handlers.NewConfirmationsHandler(confirmationsPerChain)
226+
go api.Serve(ctx, configuration.RelayerConfig.ApiAddr, signingHandler, statusHandler, confirmationsHandler)
205227

206228
sig := <-sysErr
207229
log.Info().Msgf("terminating got ` [%v] signal", sig)

chains/evm/config.go

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,46 +6,48 @@ package evm
66
import (
77
"fmt"
88
"math/big"
9+
"strconv"
910
"time"
1011

1112
"github.com/creasty/defaults"
13+
"github.com/ethereum/go-ethereum/common"
1214
"github.com/mitchellh/mapstructure"
1315

1416
"github.com/sprintertech/sprinter-signing/config/chain"
1517
)
1618

17-
type HandlerConfig struct {
18-
Address string
19-
Type string
19+
type TokenConfig struct {
20+
Address common.Address
21+
Decimals uint8
2022
}
2123

2224
type EVMConfig struct {
2325
GeneralChainConfig chain.GeneralChainConfig
2426
Admin string
2527
AcrossPool string
2628
LiqudityPool string
27-
BlockConfirmations *big.Int
28-
BlockInterval *big.Int
29-
BlockRetryInterval time.Duration
29+
Tokens map[string]TokenConfig
30+
// usd bucket -> confirmations
31+
ConfirmationsByValue map[uint64]uint64
32+
BlockInterval *big.Int
33+
BlockRetryInterval time.Duration
3034
}
3135

3236
type RawEVMConfig struct {
3337
chain.GeneralChainConfig `mapstructure:",squash"`
34-
Admin string `mapstructure:"admin"`
35-
LiqudityPool string `mapstructure:"liquidityPool"`
36-
AcrossPool string `mapstructure:"acrossPool"`
37-
BlockConfirmations int64 `mapstructure:"blockConfirmations" default:"10"`
38-
BlockInterval int64 `mapstructure:"blockInterval" default:"5"`
39-
BlockRetryInterval uint64 `mapstructure:"blockRetryInterval" default:"5"`
38+
Admin string `mapstructure:"admin"`
39+
LiqudityPool string `mapstructure:"liquidityPool"`
40+
AcrossPool string `mapstructure:"acrossPool"`
41+
Tokens map[string]interface{} `mapstructure:"tokens"`
42+
ConfirmationsByValue map[string]interface{} `mapstructure:"confirmationsByValue"`
43+
BlockInterval int64 `mapstructure:"blockInterval" default:"5"`
44+
BlockRetryInterval uint64 `mapstructure:"blockRetryInterval" default:"5"`
4045
}
4146

4247
func (c *RawEVMConfig) Validate() error {
4348
if err := c.GeneralChainConfig.Validate(); err != nil {
4449
return err
4550
}
46-
if c.BlockConfirmations < 1 {
47-
return fmt.Errorf("blockConfirmations has to be >=1")
48-
}
4951
return nil
5052
}
5153

@@ -68,6 +70,41 @@ func NewEVMConfig(chainConfig map[string]interface{}) (*EVMConfig, error) {
6870
return nil, err
6971
}
7072

73+
tokens := make(map[string]TokenConfig)
74+
for s, c := range c.Tokens {
75+
c := c.(map[string]interface{})
76+
77+
decimals, err := strconv.ParseUint(c["decimals"].(string), 10, 8)
78+
if err != nil {
79+
return nil, err
80+
}
81+
82+
tc := TokenConfig{
83+
Address: common.HexToAddress(c["address"].(string)),
84+
Decimals: uint8(decimals),
85+
}
86+
tokens[s] = tc
87+
}
88+
89+
confirmations := make(map[uint64]uint64)
90+
for usd, confirmation := range c.ConfirmationsByValue {
91+
usd, err := strconv.ParseUint(usd, 10, 64)
92+
if err != nil {
93+
return nil, err
94+
}
95+
96+
confirmation, err := strconv.ParseUint(confirmation.(string), 10, 64)
97+
if err != nil {
98+
return nil, err
99+
}
100+
101+
if confirmation < 1 {
102+
return nil, fmt.Errorf("confirmation cannot be lower than 1")
103+
}
104+
105+
confirmations[usd] = confirmation
106+
}
107+
71108
c.GeneralChainConfig.ParseFlags()
72109
config := &EVMConfig{
73110
GeneralChainConfig: c.GeneralChainConfig,
@@ -76,8 +113,10 @@ func NewEVMConfig(chainConfig map[string]interface{}) (*EVMConfig, error) {
76113
AcrossPool: c.AcrossPool,
77114
// nolint:gosec
78115
BlockRetryInterval: time.Duration(c.BlockRetryInterval) * time.Second,
79-
BlockConfirmations: big.NewInt(c.BlockConfirmations),
80116
BlockInterval: big.NewInt(c.BlockInterval),
117+
118+
ConfirmationsByValue: confirmations,
119+
Tokens: tokens,
81120
}
82121

83122
return config, nil

0 commit comments

Comments
 (0)