diff --git a/.github/workflows/deploy_stage.yml b/.github/workflows/deploy_stage.yml index 6757e545..fffb39f5 100644 --- a/.github/workflows/deploy_stage.yml +++ b/.github/workflows/deploy_stage.yml @@ -222,4 +222,4 @@ jobs: fields: repo,message,commit,author,action,job,eventName,ref,workflow # selectable (default: repo,message) env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # required - if: always() \ No newline at end of file + if: always() diff --git a/Makefile b/Makefile index a1c324c8..fc84ec99 100644 --- a/Makefile +++ b/Makefile @@ -24,16 +24,17 @@ test: ./scripts/tests.sh genmocks: - mockgen -destination=./tss/common/mock/tss.go github.com/binance-chain/tss-lib/tss Message - mockgen -destination=./tss/common/mock/communication.go -source=./tss/common/base.go -package mock_tss - mockgen -destination=./tss/keygen/mock/storer.go -source=./tss/keygen/keygen.go - mockgen -destination=./tss/keygen/mock/storer.go -source=./tss/keygen/keygen.go - mockgen --package mock_tss -destination=./tss/mock/storer.go -source=./tss/resharing/resharing.go + mockgen -destination=./tss/ecdsa/common/mock/tss.go github.com/binance-chain/tss-lib/tss Message + mockgen -destination=./tss/ecdsa/common/mock/communication.go -source=./tss/ecdsa/common/base.go -package mock_tss + mockgen --package mock_tss -destination=./tss/mock/ecdsa.go -source=./tss/ecdsa/keygen/keygen.go + mockgen --package mock_tss -destination=./tss/mock/frost.go -source=./tss/frost/keygen/keygen.go mockgen -source=./tss/coordinator.go -destination=./tss/mock/coordinator.go mockgen -source=./comm/communication.go -destination=./comm/mock/communication.go mockgen -source=./chains/evm/listener/eventHandlers/event-handler.go -destination=./chains/evm/listener/eventHandlers/mock/listener.go mockgen -source=./chains/evm/calls/events/listener.go -destination=./chains/evm/calls/events/mock/listener.go mockgen -source=./chains/substrate/listener/event-handlers.go -destination=./chains/substrate/listener/mock/handlers.go + mockgen -source=./chains/btc/listener/event-handlers.go -destination=./chains/btc/listener/mock/handlers.go + mockgen -source=./chains/btc/listener/listener.go -destination=./chains/btc/listener/mock/listener.go mockgen -source=./topology/topology.go -destination=./topology/mock/topology.go diff --git a/app/app.go b/app/app.go index da0ba8f4..4eb62463 100644 --- a/app/app.go +++ b/app/app.go @@ -15,6 +15,8 @@ import ( "time" "github.com/ChainSafe/sygma-relayer/chains" + "github.com/ChainSafe/sygma-relayer/chains/btc" + "github.com/ChainSafe/sygma-relayer/chains/btc/mempool" "github.com/ChainSafe/sygma-relayer/chains/evm" "github.com/ChainSafe/sygma-relayer/chains/evm/calls/contracts/bridge" "github.com/ChainSafe/sygma-relayer/chains/evm/calls/events" @@ -23,6 +25,7 @@ import ( hubEventHandlers "github.com/ChainSafe/sygma-relayer/chains/evm/listener/eventHandlers" "github.com/ChainSafe/sygma-relayer/chains/substrate" "github.com/ChainSafe/sygma-relayer/relayer/transfer" + propStore "github.com/ChainSafe/sygma-relayer/store" "github.com/sygmaprotocol/sygma-core/chains/evm/transactor/gas" coreSubstrate "github.com/sygmaprotocol/sygma-core/chains/substrate" "github.com/sygmaprotocol/sygma-core/crypto/secp256k1" @@ -32,6 +35,10 @@ import ( "github.com/sygmaprotocol/sygma-core/store" "github.com/sygmaprotocol/sygma-core/store/lvldb" + btcConfig "github.com/ChainSafe/sygma-relayer/chains/btc/config" + btcConnection "github.com/ChainSafe/sygma-relayer/chains/btc/connection" + btcExecutor "github.com/ChainSafe/sygma-relayer/chains/btc/executor" + btcListener "github.com/ChainSafe/sygma-relayer/chains/btc/listener" substrateExecutor "github.com/ChainSafe/sygma-relayer/chains/substrate/executor" substrateListener "github.com/ChainSafe/sygma-relayer/chains/substrate/listener" substratePallet "github.com/ChainSafe/sygma-relayer/chains/substrate/pallet" @@ -116,7 +123,6 @@ func Run() error { communication := p2p.NewCommunication(host, "p2p/sygma") electorFactory := elector.NewCoordinatorElectorFactory(host, configuration.RelayerConfig.BullyConfig) coordinator := tss.NewCoordinator(host, communication, electorFactory) - keyshareStore := keyshare.NewKeyshareStore(configuration.RelayerConfig.MpcConfig.KeysharePath) // this is temporary solution related to specifics of aws deployment // effectively it waits until old instance is killed @@ -132,6 +138,9 @@ func Run() error { } } blockstore := store.NewBlockStore(db) + keyshareStore := keyshare.NewECDSAKeyshareStore(configuration.RelayerConfig.MpcConfig.KeysharePath) + frostKeyshareStore := keyshare.NewFrostKeyshareStore(configuration.RelayerConfig.MpcConfig.FrostKeysharePath) + propStore := propStore.NewPropStore(db) // wait until executions are done and then stop further executions before exiting exitLock := &sync.RWMutex{} @@ -170,6 +179,7 @@ func Run() error { log.Info().Str("domain", config.String()).Msgf("Registering EVM domain") bridgeAddress := common.HexToAddress(config.Bridge) + frostAddress := common.HexToAddress(config.FrostKeygen) gasPricer := gas.NewLondonGasPriceClient(client, &gas.GasPricerOpts{ UpperLimitFeePerGas: config.MaxGasPrice, GasPriceFactor: config.GasMultiplier, @@ -213,6 +223,7 @@ func Run() error { l := log.With().Str("chain", fmt.Sprintf("%v", config.GeneralChainConfig.Name)).Uint8("domainID", *config.GeneralChainConfig.Id) eventHandlers = append(eventHandlers, hubEventHandlers.NewDepositEventHandler(depositListener, depositHandler, bridgeAddress, *config.GeneralChainConfig.Id, msgChan)) eventHandlers = append(eventHandlers, hubEventHandlers.NewKeygenEventHandler(l, tssListener, coordinator, host, communication, keyshareStore, bridgeAddress, networkTopology.Threshold)) + eventHandlers = append(eventHandlers, hubEventHandlers.NewFrostKeygenEventHandler(l, tssListener, coordinator, host, communication, frostKeyshareStore, frostAddress, networkTopology.Threshold)) eventHandlers = append(eventHandlers, hubEventHandlers.NewRefreshEventHandler(l, topologyProvider, topologyStore, tssListener, coordinator, host, communication, connectionGate, keyshareStore, bridgeAddress)) eventHandlers = append(eventHandlers, hubEventHandlers.NewRetryEventHandler(l, tssListener, depositHandler, bridgeAddress, *config.GeneralChainConfig.Id, config.BlockConfirmations, msgChan)) evmListener := listener.NewEVMListener(client, eventHandlers, blockstore, sygmaMetrics, *config.GeneralChainConfig.Id, config.BlockRetryInterval, config.BlockConfirmations, config.BlockInterval) @@ -291,6 +302,51 @@ func Run() error { domains[*config.GeneralChainConfig.Id] = substrateChain } + case "btc": + { + log.Info().Msgf("Registering btc domain") + config, err := btcConfig.NewBtcConfig(chainConfig) + if err != nil { + panic(err) + } + + conn, err := btcConnection.NewBtcConnection( + config.GeneralChainConfig.Endpoint, + config.Username, + config.Password, + false) + if err != nil { + panic(err) + } + + l := log.With().Str("chain", fmt.Sprintf("%v", config.GeneralChainConfig.Name)).Uint8("domainID", *config.GeneralChainConfig.Id) + depositHandler := &btcListener.BtcDepositHandler{} + eventHandlers := make([]btcListener.EventHandler, 0) + resources := make(map[[32]byte]btcConfig.Resource) + for _, resource := range config.Resources { + resources[resource.ResourceID] = resource + eventHandlers = append(eventHandlers, btcListener.NewFungibleTransferEventHandler(l, *config.GeneralChainConfig.Id, depositHandler, msgChan, conn, resource)) + } + listener := btcListener.NewBtcListener(conn, eventHandlers, config, blockstore) + + mempool := mempool.NewMempoolAPI(config.MempoolUrl) + mh := &btcExecutor.BtcMessageHandler{} + executor := btcExecutor.NewExecutor( + propStore, + host, + communication, + coordinator, + frostKeyshareStore, + conn, + mempool, + resources, + config.Network, + exitLock) + + btcChain := btc.NewBtcChain(listener, executor, mh, *config.GeneralChainConfig.Id) + domains[*config.GeneralChainConfig.Id] = btcChain + + } default: panic(fmt.Errorf("type '%s' not recognized", chainConfig["type"])) } diff --git a/chains/btc/chain.go b/chains/btc/chain.go new file mode 100644 index 00000000..6a524e61 --- /dev/null +++ b/chains/btc/chain.go @@ -0,0 +1,72 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package btc + +import ( + "context" + "math/big" + + "github.com/ChainSafe/sygma-relayer/chains/btc/executor" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/sygmaprotocol/sygma-core/relayer/message" + "github.com/sygmaprotocol/sygma-core/relayer/proposal" +) + +type BatchProposalExecutor interface { + Execute(msgs []*message.Message) error +} +type EventListener interface { + ListenToEvents(ctx context.Context, startBlock *big.Int) +} +type BtcChain struct { + id uint8 + + listener EventListener + executor *executor.Executor + mh *executor.BtcMessageHandler + + startBlock *big.Int + logger zerolog.Logger +} + +func NewBtcChain( + listener EventListener, + executor *executor.Executor, + mh *executor.BtcMessageHandler, + id uint8, +) *BtcChain { + return &BtcChain{ + listener: listener, + executor: executor, + mh: mh, + id: id, + + logger: log.With().Uint8("domainID", id).Logger()} +} + +func (c *BtcChain) Write(props []*proposal.Proposal) error { + err := c.executor.Execute(props) + if err != nil { + c.logger.Err(err).Str("messageID", props[0].MessageID).Msgf("error writing proposals %+v on network %d", props, c.DomainID()) + return err + } + + return nil +} + +func (c *BtcChain) ReceiveMessage(m *message.Message) (*proposal.Proposal, error) { + return c.mh.HandleMessage(m) +} + +// PollEvents is the goroutine that polls blocks and searches Deposit events in them. +// Events are then sent to eventsChan. +func (c *BtcChain) PollEvents(ctx context.Context) { + c.logger.Info().Str("startBlock", c.startBlock.String()).Msg("Polling Blocks...") + go c.listener.ListenToEvents(ctx, c.startBlock) +} + +func (c *BtcChain) DomainID() uint8 { + return c.id +} diff --git a/chains/btc/config/config.go b/chains/btc/config/config.go new file mode 100644 index 00000000..c43abc9b --- /dev/null +++ b/chains/btc/config/config.go @@ -0,0 +1,158 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package config + +import ( + "encoding/hex" + "fmt" + "math/big" + "time" + + "github.com/ChainSafe/sygma-relayer/config/chain" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/creasty/defaults" + "github.com/mitchellh/mapstructure" +) + +type RawResource struct { + Address string + ResourceID string + Tweak string + Script string +} + +type Resource struct { + Address btcutil.Address + ResourceID [32]byte + Tweak string + Script []byte +} + +type RawBtcConfig struct { + chain.GeneralChainConfig `mapstructure:",squash"` + Resources []RawResource `mapstrcture:"resources"` + StartBlock int64 `mapstructure:"startBlock"` + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` + BlockInterval int64 `mapstructure:"blockInterval" default:"5"` + BlockRetryInterval uint64 `mapstructure:"blockRetryInterval" default:"5"` + BlockConfirmations int64 `mapstructure:"blockConfirmations" default:"10"` + Network string `mapstructure:"network" default:"mainnet"` + MempoolUrl string `mapstructure:"mempoolUrl"` +} + +func (c *RawBtcConfig) Validate() error { + if err := c.GeneralChainConfig.Validate(); err != nil { + return err + } + + if c.BlockConfirmations != 0 && c.BlockConfirmations < 1 { + return fmt.Errorf("blockConfirmations has to be >=1") + } + + if c.Username == "" { + return fmt.Errorf("required field chain.Username empty for chain %v", *c.Id) + } + + if c.Password == "" { + return fmt.Errorf("required field chain.Password empty for chain %v", *c.Id) + } + return nil +} + +type BtcConfig struct { + GeneralChainConfig chain.GeneralChainConfig + Resources []Resource + Username string + Password string + StartBlock *big.Int + BlockInterval *big.Int + BlockRetryInterval time.Duration + BlockConfirmations *big.Int + Tweak string + Script []byte + MempoolUrl string + Network chaincfg.Params +} + +// NewBtcConfig decodes and validates an instance of an BtcConfig from +// raw chain config +func NewBtcConfig(chainConfig map[string]interface{}) (*BtcConfig, error) { + var c RawBtcConfig + err := mapstructure.Decode(chainConfig, &c) + if err != nil { + return nil, err + } + + err = defaults.Set(&c) + if err != nil { + return nil, err + } + + err = c.Validate() + if err != nil { + return nil, err + } + + networkParams, err := networkParams(c.Network) + if err != nil { + return nil, err + } + + resources := make([]Resource, len(c.Resources)) + for i, r := range c.Resources { + scriptBytes, err := hex.DecodeString(r.Script) + if err != nil { + return nil, err + } + + address, err := btcutil.DecodeAddress(r.Address, &networkParams) + if err != nil { + return nil, err + } + resourceBytes, err := hex.DecodeString(r.ResourceID[2:]) + if err != nil { + panic(err) + } + var resource32Bytes [32]byte + copy(resource32Bytes[:], resourceBytes) + resources[i] = Resource{ + Address: address, + ResourceID: resource32Bytes, + Script: scriptBytes, + Tweak: r.Tweak, + } + } + + c.GeneralChainConfig.ParseFlags() + config := &BtcConfig{ + GeneralChainConfig: c.GeneralChainConfig, + StartBlock: big.NewInt(c.StartBlock), + BlockConfirmations: big.NewInt(c.BlockConfirmations), + BlockInterval: big.NewInt(c.BlockInterval), + BlockRetryInterval: time.Duration(c.BlockRetryInterval) * time.Second, + Username: c.Username, + Password: c.Password, + Network: networkParams, + MempoolUrl: c.MempoolUrl, + Resources: resources, + } + return config, nil +} + +func networkParams(network string) (chaincfg.Params, error) { + switch network { + case "mainnet": + return chaincfg.MainNetParams, nil + case "testnet": + return chaincfg.TestNet3Params, nil + case "regtest": + return chaincfg.RegressionNetParams, nil + case "signet": + return chaincfg.SigNetParams, nil + default: + return chaincfg.Params{}, fmt.Errorf("unknown network %s", network) + } +} diff --git a/chains/btc/config/config_test.go b/chains/btc/config/config_test.go new file mode 100644 index 00000000..2dfaf0df --- /dev/null +++ b/chains/btc/config/config_test.go @@ -0,0 +1,142 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package config_test + +import ( + "encoding/hex" + "math/big" + "testing" + "time" + + "github.com/ChainSafe/sygma-relayer/chains/btc/config" + "github.com/ChainSafe/sygma-relayer/chains/btc/listener" + "github.com/ChainSafe/sygma-relayer/config/chain" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/suite" +) + +type NewBtcConfigTestSuite struct { + suite.Suite +} + +func TestRunNewBtcConfigTestSuite(t *testing.T) { + suite.Run(t, new(NewBtcConfigTestSuite)) +} + +func (s *NewBtcConfigTestSuite) Test_FailedDecode() { + _, err := config.NewBtcConfig(map[string]interface{}{ + "gasLimit": "invalid", + }) + + s.NotNil(err) +} + +func (s *NewBtcConfigTestSuite) Test_FailedGeneralConfigValidation() { + _, err := config.NewBtcConfig(map[string]interface{}{}) + + s.NotNil(err) +} + +func (s *NewBtcConfigTestSuite) Test_FailedBtcConfigValidation() { + _, err := config.NewBtcConfig(map[string]interface{}{ + "id": 1, + "endpoint": "", + "name": "btc1", + }) + + s.NotNil(err) +} + +func (s *NewBtcConfigTestSuite) Test_InvalidBlockConfirmation() { + _, err := config.NewBtcConfig(map[string]interface{}{ + "id": 1, + "endpoint": "ws://domain.com", + "name": "btc1", + "blockConfirmations": -1, + }) + + s.NotNil(err) + s.Equal(err.Error(), "blockConfirmations has to be >=1") +} + +func (s *NewBtcConfigTestSuite) Test_InvalidUsername() { + _, err := config.NewBtcConfig(map[string]interface{}{ + "id": 1, + "endpoint": "ws://domain.com", + "name": "btc1", + "password": "pass123", + + "blockConfirmations": 1, + }) + + s.NotNil(err) + s.Equal(err.Error(), "required field chain.Username empty for chain 1") +} + +func (s *NewBtcConfigTestSuite) Test_InvalidPassword() { + _, err := config.NewBtcConfig(map[string]interface{}{ + "id": 1, + "endpoint": "ws://domain.com", + "name": "btc1", + "username": "pass123", + + "blockConfirmations": 1, + }) + + s.NotNil(err) + s.Equal(err.Error(), "required field chain.Password empty for chain 1") +} + +func (s *NewBtcConfigTestSuite) Test_ValidConfig() { + expectedResource := listener.SliceTo32Bytes(common.LeftPadBytes([]byte{3}, 31)) + expectedAddress, _ := btcutil.DecodeAddress("tb1qln69zuhdunc9stwfh6t7adexxrcr04ppy6thgm", &chaincfg.TestNet3Params) + expectedScript, _ := hex.DecodeString("51206a698882348433b57d549d6344f74500fcd13ad8d2200cdf89f8e39e5cafa7d5") + + rawConfig := map[string]interface{}{ + "id": 1, + "endpoint": "ws://domain.com", + "name": "btc1", + "username": "username", + "password": "pass123", + "network": "testnet", + "resources": []interface{}{ + config.RawResource{ + Address: "tb1qln69zuhdunc9stwfh6t7adexxrcr04ppy6thgm", + ResourceID: "0x0000000000000000000000000000000000000000000000000000000000000300", + Script: "51206a698882348433b57d549d6344f74500fcd13ad8d2200cdf89f8e39e5cafa7d5", + Tweak: "tweak", + }, + }, + } + + actualConfig, err := config.NewBtcConfig(rawConfig) + + id := new(uint8) + *id = 1 + s.Nil(err) + s.Equal(*actualConfig, config.BtcConfig{ + GeneralChainConfig: chain.GeneralChainConfig{ + Name: "btc1", + Endpoint: "ws://domain.com", + Id: id, + }, + Username: "username", + Password: "pass123", + StartBlock: big.NewInt(0), + BlockConfirmations: big.NewInt(10), + BlockInterval: big.NewInt(5), + BlockRetryInterval: time.Duration(5) * time.Second, + Network: chaincfg.TestNet3Params, + Resources: []config.Resource{ + { + Address: expectedAddress, + ResourceID: expectedResource, + Script: expectedScript, + Tweak: "tweak", + }, + }, + }) +} diff --git a/chains/btc/connection/connection.go b/chains/btc/connection/connection.go new file mode 100644 index 00000000..99fe9262 --- /dev/null +++ b/chains/btc/connection/connection.go @@ -0,0 +1,39 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package connection + +import ( + "github.com/btcsuite/btcd/rpcclient" + "github.com/rs/zerolog/log" +) + +type Connection struct { + *rpcclient.Client +} + +func NewBtcConnection(url string, username string, password string, disableTLS bool) (*Connection, error) { + // Connect to a Bitcoin node using RPC + connConfig := &rpcclient.ConnConfig{ + HTTPPostMode: true, + Host: url, + User: username, + Pass: password, + DisableTLS: disableTLS, + } + + client, err := rpcclient.New(connConfig, nil) + if err != nil { + return nil, err + } + + info, err := client.GetBlockChainInfo() + if err != nil { + return nil, err + } + log.Debug().Msgf("Connected to bitcoin node %s ", info.Chain) + + return &Connection{ + Client: client, + }, nil +} diff --git a/chains/btc/executor/executor.go b/chains/btc/executor/executor.go new file mode 100644 index 00000000..0e2ff953 --- /dev/null +++ b/chains/btc/executor/executor.go @@ -0,0 +1,357 @@ +package executor + +import ( + "context" + "encoding/hex" + "fmt" + "math/big" + "sync" + "time" + + "github.com/ChainSafe/sygma-relayer/chains/btc/config" + "github.com/ChainSafe/sygma-relayer/chains/btc/connection" + "github.com/ChainSafe/sygma-relayer/chains/btc/mempool" + "github.com/ChainSafe/sygma-relayer/comm" + "github.com/ChainSafe/sygma-relayer/store" + "github.com/ChainSafe/sygma-relayer/tss" + "github.com/ChainSafe/sygma-relayer/tss/frost/signing" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/libp2p/go-libp2p/core/host" + "github.com/rs/zerolog/log" + "github.com/sourcegraph/conc/pool" + "github.com/sygmaprotocol/sygma-core/relayer/proposal" + "github.com/taurusgroup/multi-party-sig/pkg/taproot" + "go.uber.org/zap/buffer" +) + +var ( + signingTimeout = 30 * time.Minute + + INPUT_SIZE = 180 + OUTPUT_SIZE = 34 +) + +type MempoolAPI interface { + RecommendedFee() (*mempool.Fee, error) + Utxos(address string) ([]mempool.Utxo, error) +} + +type PropStorer interface { + StorePropStatus(source, destination uint8, depositNonce uint64, status store.PropStatus) error + PropStatus(source, destination uint8, depositNonce uint64) (store.PropStatus, error) +} + +type Executor struct { + coordinator *tss.Coordinator + host host.Host + comm comm.Communication + + conn *connection.Connection + resources map[[32]byte]config.Resource + chainCfg chaincfg.Params + mempool MempoolAPI + fetcher signing.SaveDataFetcher + + propStorer PropStorer + propMutex sync.Mutex + + exitLock *sync.RWMutex +} + +func NewExecutor( + propStorer PropStorer, + host host.Host, + comm comm.Communication, + coordinator *tss.Coordinator, + fetcher signing.SaveDataFetcher, + conn *connection.Connection, + mempool MempoolAPI, + resources map[[32]byte]config.Resource, + chainCfg chaincfg.Params, + exitLock *sync.RWMutex, +) *Executor { + return &Executor{ + propStorer: propStorer, + host: host, + comm: comm, + coordinator: coordinator, + exitLock: exitLock, + fetcher: fetcher, + conn: conn, + resources: resources, + mempool: mempool, + chainCfg: chainCfg, + } +} + +// Execute starts a signing process and executes proposals when signature is generated +func (e *Executor) Execute(proposals []*proposal.Proposal) error { + e.exitLock.RLock() + defer e.exitLock.RUnlock() + + sessionID := proposals[0].MessageID + props, err := e.proposalsForExecution(proposals) + if err != nil { + return err + } + if len(props) == 0 { + return nil + } + resource, ok := e.resources[props[0].Data.ResourceId] + if !ok { + return fmt.Errorf("no address for resource") + } + + tx, utxos, err := e.rawTx(props, resource) + if err != nil { + return err + } + + sigChn := make(chan interface{}) + p := pool.New().WithErrors() + executionContext, cancelExecution := context.WithCancel(context.Background()) + watchContext, cancelWatch := context.WithCancel(context.Background()) + defer cancelWatch() + p.Go(func() error { return e.watchExecution(watchContext, cancelExecution, tx, props, sigChn, sessionID) }) + prevOuts := make(map[wire.OutPoint]*wire.TxOut) + for _, utxo := range utxos { + txOut := wire.NewTxOut(int64(utxo.Value), resource.Script) + hash, err := chainhash.NewHashFromStr(utxo.TxID) + if err != nil { + return err + } + prevOuts[*wire.NewOutPoint(hash, utxo.Vout)] = txOut + } + prevOutputFetcher := txscript.NewMultiPrevOutFetcher(prevOuts) + sigHashes := txscript.NewTxSigHashes(tx, prevOutputFetcher) + + // we need to sign each input individually + for i := range tx.TxIn { + txHash, err := txscript.CalcTaprootSignatureHash(sigHashes, txscript.SigHashDefault, tx, i, prevOutputFetcher) + if err != nil { + return err + } + p.Go(func() error { + msg := new(big.Int) + msg.SetBytes(txHash[:]) + signing, err := signing.NewSigning( + i, + msg, + resource.Tweak, + fmt.Sprintf("%s-%d", sessionID, i), + e.host, + e.comm, + e.fetcher) + if err != nil { + return err + } + return e.coordinator.Execute(executionContext, signing, sigChn) + }) + } + return p.Wait() +} + +func (e *Executor) watchExecution(ctx context.Context, cancelExecution context.CancelFunc, tx *wire.MsgTx, proposals []*BtcTransferProposal, sigChn chan interface{}, sessionID string) error { + timeout := time.NewTicker(signingTimeout) + defer timeout.Stop() + defer cancelExecution() + signatures := make([]taproot.Signature, len(tx.TxIn)) + + for { + select { + case sigResult := <-sigChn: + { + if sigResult == nil { + continue + } + signatureData := sigResult.(signing.Signature) + signatures[signatureData.Id] = signatureData.Signature + if !e.signaturesFilled(signatures) { + continue + } + cancelExecution() + + hash, err := e.sendTx(tx, signatures) + if err != nil { + _ = e.comm.Broadcast(e.host.Peerstore().Peers(), []byte{}, comm.TssFailMsg, sessionID) + e.storeProposalsStatus(proposals, store.FailedProp) + return err + } + + log.Info().Str("SessionID", sessionID).Msgf("Sent proposals execution with hash: %s", hash) + } + case <-timeout.C: + { + return fmt.Errorf("execution timed out in %s", signingTimeout) + } + case <-ctx.Done(): + { + return nil + } + } + } +} + +func (e *Executor) rawTx(proposals []*BtcTransferProposal, resource config.Resource) (*wire.MsgTx, []mempool.Utxo, error) { + tx := wire.NewMsgTx(wire.TxVersion) + outputAmount, err := e.outputs(tx, proposals) + if err != nil { + return nil, nil, err + } + inputAmount, utxos, err := e.inputs(tx, resource.Address, outputAmount) + if err != nil { + return nil, nil, err + } + if inputAmount < outputAmount { + return nil, nil, fmt.Errorf("utxo input amount %d less than output amount %d", inputAmount, outputAmount) + } + fee, err := e.fee(int64(len(utxos)), int64(len(proposals))+1) + if err != nil { + return nil, nil, err + } + + returnAmount := int64(inputAmount) - fee - outputAmount + if returnAmount > 0 { + // return extra funds + returnScript, err := txscript.PayToAddrScript(resource.Address) + if err != nil { + return nil, nil, err + } + txOut := wire.NewTxOut(returnAmount, returnScript) + tx.AddTxOut(txOut) + } + return tx, utxos, err +} + +func (e *Executor) outputs(tx *wire.MsgTx, proposals []*BtcTransferProposal) (int64, error) { + outputAmount := int64(0) + for _, prop := range proposals { + addr, err := btcutil.DecodeAddress(prop.Data.Recipient, &e.chainCfg) + if err != nil { + return 0, err + } + destinationAddrByte, err := txscript.PayToAddrScript(addr) + if err != nil { + return 0, err + } + txOut := wire.NewTxOut(prop.Data.Amount, destinationAddrByte) + tx.AddTxOut(txOut) + outputAmount += prop.Data.Amount + } + return outputAmount, nil +} + +func (e *Executor) inputs(tx *wire.MsgTx, address btcutil.Address, outputAmount int64) (int64, []mempool.Utxo, error) { + usedUtxos := make([]mempool.Utxo, 0) + inputAmount := int64(0) + utxos, err := e.mempool.Utxos(address.String()) + if err != nil { + return 0, nil, err + } + for _, utxo := range utxos { + previousTxHash, err := chainhash.NewHashFromStr(utxo.TxID) + if err != nil { + return 0, nil, err + } + outPoint := wire.NewOutPoint(previousTxHash, utxo.Vout) + txIn := wire.NewTxIn(outPoint, nil, nil) + tx.AddTxIn(txIn) + + usedUtxos = append(usedUtxos, utxo) + inputAmount += int64(utxo.Value) + if inputAmount > outputAmount { + break + } + } + return inputAmount, usedUtxos, nil +} + +func (e *Executor) fee(numOfInputs, numOfOutputs int64) (int64, error) { + recommendedFee, err := e.mempool.RecommendedFee() + if err != nil { + return 0, err + } + return (numOfInputs*int64(INPUT_SIZE) + numOfOutputs*int64(OUTPUT_SIZE)) * recommendedFee.EconomyFee, nil +} + +func (e *Executor) sendTx(tx *wire.MsgTx, signatures []taproot.Signature) (*chainhash.Hash, error) { + for i, sig := range signatures { + tx.TxIn[i].Witness = wire.TxWitness{sig} + } + + var buf buffer.Buffer + err := tx.Serialize(&buf) + if err != nil { + return nil, err + } + bytes := buf.Bytes() + log.Debug().Msgf("Assembled raw transaction %s", hex.EncodeToString(bytes)) + return e.conn.SendRawTransaction(tx, true) +} + +func (e *Executor) signaturesFilled(signatures []taproot.Signature) bool { + for _, signature := range signatures { + if len([]byte(signature)) == 0 { + return false + } + } + + return true +} + +func (e *Executor) proposalsForExecution(proposals []*proposal.Proposal) ([]*BtcTransferProposal, error) { + e.propMutex.Lock() + props := make([]*BtcTransferProposal, 0) + for _, prop := range proposals { + executed, err := e.isExecuted(prop) + if err != nil { + return props, err + } + + if executed { + log.Info().Msgf("Proposal %s already executed", fmt.Sprintf("%d-%d-%d", prop.Source, prop.Destination, prop.Data.(BtcTransferProposalData).DepositNonce)) + continue + } + + err = e.propStorer.StorePropStatus(prop.Source, prop.Destination, prop.Data.(BtcTransferProposalData).DepositNonce, store.PendingProp) + if err != nil { + return props, err + } + props = append(props, &BtcTransferProposal{ + Source: prop.Source, + Destination: prop.Destination, + Data: prop.Data.(BtcTransferProposalData), + }) + } + e.propMutex.Unlock() + return props, nil +} + +func (e *Executor) isExecuted(prop *proposal.Proposal) (bool, error) { + status, err := e.propStorer.PropStatus(prop.Source, prop.Destination, prop.Data.(BtcTransferProposalData).DepositNonce) + if err != nil { + return true, err + } + + if status == store.MissingProp || status == store.FailedProp { + return false, nil + } + return true, err +} + +func (e *Executor) storeProposalsStatus(props []*BtcTransferProposal, status store.PropStatus) { + for _, prop := range props { + err := e.propStorer.StorePropStatus( + prop.Source, + prop.Destination, + prop.Data.DepositNonce, + status) + if err != nil { + log.Err(err).Msgf("Failed storing proposal %+v status %s", prop, status) + } + } +} diff --git a/chains/btc/executor/message-handler.go b/chains/btc/executor/message-handler.go new file mode 100644 index 00000000..87620878 --- /dev/null +++ b/chains/btc/executor/message-handler.go @@ -0,0 +1,72 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package executor + +import ( + "errors" + "math/big" + + "github.com/sygmaprotocol/sygma-core/relayer/message" + "github.com/sygmaprotocol/sygma-core/relayer/proposal" + + "github.com/ChainSafe/sygma-relayer/relayer/transfer" +) + +type BtcTransferProposalData struct { + Amount int64 + Recipient string + DepositNonce uint64 + ResourceId [32]byte +} + +type BtcTransferProposal struct { + Source uint8 + Destination uint8 + Data BtcTransferProposalData +} + +type BtcMessageHandler struct{} + +func (h *BtcMessageHandler) HandleMessage(msg *message.Message) (*proposal.Proposal, error) { + transferMessage := &transfer.TransferMessage{ + Source: msg.Source, + Destination: msg.Destination, + Data: msg.Data.(transfer.TransferMessageData), + Type: msg.Type, + ID: msg.ID, + } + + switch transferMessage.Data.Type { + case transfer.FungibleTransfer: + return ERC20MessageHandler(transferMessage) + } + return nil, errors.New("wrong message type passed while handling message") +} + +func ERC20MessageHandler(msg *transfer.TransferMessage) (*proposal.Proposal, error) { + if len(msg.Data.Payload) != 2 { + return nil, errors.New("malformed payload. Len of payload should be 2") + } + amount, ok := msg.Data.Payload[0].([]byte) + if !ok { + return nil, errors.New("wrong payload amount format") + } + recipient, ok := msg.Data.Payload[1].([]byte) + if !ok { + return nil, errors.New("wrong payload recipient format") + } + bigAmount := new(big.Int).SetBytes(amount) + + // remove 10 decimal places to match Bitcoin network + divisor := new(big.Int) + divisor.Exp(big.NewInt(10), big.NewInt(10), nil) + bigAmount.Div(bigAmount, divisor) + + return proposal.NewProposal(msg.Source, msg.Destination, BtcTransferProposalData{ + Amount: bigAmount.Int64(), + Recipient: string(recipient), + DepositNonce: msg.Data.DepositNonce, + ResourceId: msg.Data.ResourceId, + }, msg.ID, transfer.TransferProposalType), nil +} diff --git a/chains/btc/executor/message-handler_test.go b/chains/btc/executor/message-handler_test.go new file mode 100644 index 00000000..d66f5409 --- /dev/null +++ b/chains/btc/executor/message-handler_test.go @@ -0,0 +1,178 @@ +package executor_test + +import ( + "math/big" + "testing" + + "github.com/ChainSafe/sygma-relayer/chains/btc/executor" + "github.com/ChainSafe/sygma-relayer/relayer/transfer" + "github.com/stretchr/testify/suite" + "github.com/sygmaprotocol/sygma-core/relayer/message" + "github.com/sygmaprotocol/sygma-core/relayer/proposal" +) + +type BtcMessageHandlerTestSuite struct { + suite.Suite +} + +func TestRunBtcMessageHandlerTestSuite(t *testing.T) { + suite.Run(t, new(BtcMessageHandlerTestSuite)) +} + +func (s *BtcMessageHandlerTestSuite) Test_ERC20HandleMessage_ValidMessage() { + message := &message.Message{ + Source: 1, + Destination: 0, + Data: transfer.TransferMessageData{ + DepositNonce: 1, + ResourceId: [32]byte{0}, + Payload: []interface{}{ + big.NewInt(100000045678).Bytes(), // amount + []byte("tb1pffdrehs8455lgnwquggf4dzf6jduz8v7d2usflyujq4ggh4jaapqpfjj83"), + }, + Type: transfer.FungibleTransfer, + }, + Type: transfer.TransferMessageType, + } + + mh := executor.BtcMessageHandler{} + prop, err := mh.HandleMessage(message) + + s.Nil(err) + s.NotNil(prop) + s.Equal(prop, &proposal.Proposal{ + Source: 1, + Destination: 0, + Data: executor.BtcTransferProposalData{ + Amount: 10, + Recipient: "tb1pffdrehs8455lgnwquggf4dzf6jduz8v7d2usflyujq4ggh4jaapqpfjj83", + DepositNonce: 1, + }, + Type: transfer.TransferProposalType, + }) +} + +func (s *BtcMessageHandlerTestSuite) Test_ERC20HandleMessage_ValidMessage_Dust() { + message := &message.Message{ + Source: 1, + Destination: 0, + Data: transfer.TransferMessageData{ + DepositNonce: 1, + ResourceId: [32]byte{0}, + Payload: []interface{}{ + big.NewInt(1).Bytes(), // amount + []byte("tb1pffdrehs8455lgnwquggf4dzf6jduz8v7d2usflyujq4ggh4jaapqpfjj83"), + }, + Type: transfer.FungibleTransfer, + }, + Type: transfer.TransferMessageType, + } + + mh := executor.BtcMessageHandler{} + prop, err := mh.HandleMessage(message) + + s.Nil(err) + s.NotNil(prop) + s.Equal(prop, &proposal.Proposal{ + Source: 1, + Destination: 0, + Data: executor.BtcTransferProposalData{ + Amount: 0, + Recipient: "tb1pffdrehs8455lgnwquggf4dzf6jduz8v7d2usflyujq4ggh4jaapqpfjj83", + DepositNonce: 1, + }, + Type: transfer.TransferProposalType, + }) +} + +func (s *BtcMessageHandlerTestSuite) Test_ERC20HandleMessage_IncorrectDataLen() { + message := &message.Message{ + Source: 1, + Destination: 0, + Data: transfer.TransferMessageData{ + DepositNonce: 1, + ResourceId: [32]byte{0}, + Payload: []interface{}{ + []byte{2}, // amount + }, + Type: transfer.FungibleTransfer, + }, + Type: transfer.TransferMessageType, + } + + mh := executor.BtcMessageHandler{} + prop, err := mh.HandleMessage(message) + + s.Nil(prop) + s.NotNil(err) +} + +func (s *BtcMessageHandlerTestSuite) Test_ERC20HandleMessage_IncorrectAmount() { + + message := &message.Message{ + Source: 1, + Destination: 0, + Data: transfer.TransferMessageData{ + DepositNonce: 1, + ResourceId: [32]byte{0}, + Payload: []interface{}{ + "incorrectAmount", // amount + []byte{241, 229, 143, 177, 119, 4, 194, 218, 132, 121, 165, 51, 249, 250, 212, 173, 9, 147, 202, 107}, // recipientAddress + }, + Type: transfer.FungibleTransfer, + }, + Type: transfer.TransferMessageType, + } + + mh := executor.BtcMessageHandler{} + prop, err := mh.HandleMessage(message) + + s.Nil(prop) + s.NotNil(err) +} + +func (s *BtcMessageHandlerTestSuite) Test_ERC20HandleMessage_IncorrectRecipient() { + message := &message.Message{ + Source: 1, + Destination: 0, + Data: transfer.TransferMessageData{ + DepositNonce: 1, + ResourceId: [32]byte{0}, + Payload: []interface{}{ + []byte{2}, // amount + "incorrectRecipient", // recipientAddress + }, + Type: transfer.FungibleTransfer, + }, + Type: transfer.TransferMessageType, + } + + mh := executor.BtcMessageHandler{} + prop, err := mh.HandleMessage(message) + + s.Nil(prop) + s.NotNil(err) +} + +func (s *BtcMessageHandlerTestSuite) Test_HandleMessage_InvalidType() { + message := &message.Message{ + Source: 1, + Destination: 0, + Data: transfer.TransferMessageData{ + DepositNonce: 1, + ResourceId: [32]byte{0}, + Payload: []interface{}{ + []byte{2}, // amount + "incorrectRecipient", // recipientAddress + }, + Type: transfer.NonFungibleTransfer, + }, + Type: transfer.TransferMessageType, + } + + mh := executor.BtcMessageHandler{} + prop, err := mh.HandleMessage(message) + + s.Nil(prop) + s.NotNil(err) +} diff --git a/chains/btc/listener/deposit-handler.go b/chains/btc/listener/deposit-handler.go new file mode 100644 index 00000000..278ceacf --- /dev/null +++ b/chains/btc/listener/deposit-handler.go @@ -0,0 +1,57 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package listener + +import ( + "fmt" + "math/big" + "strconv" + "strings" + + "github.com/ChainSafe/sygma-relayer/relayer/transfer" + "github.com/ethereum/go-ethereum/common" + "github.com/sygmaprotocol/sygma-core/relayer/message" +) + +type BtcDepositHandler struct{} + +// NewBtcDepositHandler creates an instance of BtcDepositHandler that contains +// handler functions for processing deposit events +func NewBtcDepositHandler() *BtcDepositHandler { + return &BtcDepositHandler{} +} + +func (e *BtcDepositHandler) HandleDeposit(sourceID uint8, + depositNonce uint64, + resourceID [32]byte, + amount *big.Int, + data string, + blockNumber *big.Int, +) (*message.Message, error) { + // data is composed of recieverEVMAddress_destinationDomainID + parsedData := strings.Split(data, "_") + evmAdd := common.HexToAddress(parsedData[0]).Bytes() + destDomainID, err := strconv.ParseUint(parsedData[1], 10, 8) + if err != nil { + return nil, err + } + + // add 10 decimal places (8->18) + multiplier := new(big.Int) + multiplier.Exp(big.NewInt(10), big.NewInt(10), nil) + amount.Mul(amount, multiplier) + payload := []interface{}{ + amount.Bytes(), + evmAdd, + } + + messageID := fmt.Sprintf("%d-%d-%d", sourceID, destDomainID, blockNumber) + return message.NewMessage(sourceID, uint8(destDomainID), transfer.TransferMessageData{ + DepositNonce: depositNonce, + ResourceId: resourceID, + Metadata: nil, + Payload: payload, + Type: transfer.FungibleTransfer, + }, messageID, transfer.TransferMessageType), nil +} diff --git a/chains/btc/listener/deposit-handler_test.go b/chains/btc/listener/deposit-handler_test.go new file mode 100644 index 00000000..fca05717 --- /dev/null +++ b/chains/btc/listener/deposit-handler_test.go @@ -0,0 +1,89 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package listener_test + +import ( + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/sygmaprotocol/sygma-core/relayer/message" + + "math/big" + "testing" + + "github.com/ChainSafe/sygma-relayer/chains/btc/listener" + "github.com/ChainSafe/sygma-relayer/relayer/transfer" + "github.com/stretchr/testify/suite" +) + +type Erc20HandlerTestSuite struct { + suite.Suite +} + +func TestRunErc20HandlerTestSuite(t *testing.T) { + suite.Run(t, new(Erc20HandlerTestSuite)) +} + +func (s *Erc20HandlerTestSuite) Test_Erc20HandleEvent() { + deposit := &listener.Deposit{ + SenderAddress: "senderAddress", + ResourceID: [32]byte{0}, + Amount: big.NewInt(100), + Data: "0x1c3A03D04c026b1f4B4208D2ce053c5686E6FB8d_1", + } + + sourceID := uint8(1) + + expectedAmount := big.NewInt(1000000000000) + blockNumber := big.NewInt(100) + depositNonce := uint64(1) + dat := strings.Split(deposit.Data, "_") + evmAdd := common.HexToAddress(dat[0]).Bytes() + messageID := fmt.Sprintf("%d-%d-%d", sourceID, 1, blockNumber) + + expected := &message.Message{ + Source: sourceID, + Destination: uint8(1), + Data: transfer.TransferMessageData{ + DepositNonce: depositNonce, + ResourceId: deposit.ResourceID, + Payload: []interface{}{ + expectedAmount.Bytes(), + evmAdd, + }, + Type: transfer.FungibleTransfer, + }, + Type: transfer.TransferMessageType, + ID: messageID, + } + + btcDepositHandler := listener.NewBtcDepositHandler() + message, err := btcDepositHandler.HandleDeposit(sourceID, depositNonce, deposit.ResourceID, deposit.Amount, deposit.Data, blockNumber) + + s.Nil(err) + s.NotNil(message) + s.Equal(message, expected) +} + +func (s *Erc20HandlerTestSuite) Test_Erc20HandleEvent_InvalidDestinationDomainID() { + + deposit := &listener.Deposit{ + SenderAddress: "senderAddress", + ResourceID: [32]byte{0}, + Amount: big.NewInt(100), + Data: "0x1c3A03D04c026b1f4B4208D2ce053c5686E6FB8d_InvalidDestinationDomainID", + } + + sourceID := uint8(1) + + blockNumber := big.NewInt(100) + depositNonce := uint64(1) + + btcDepositHandler := listener.NewBtcDepositHandler() + message, err := btcDepositHandler.HandleDeposit(sourceID, depositNonce, deposit.ResourceID, deposit.Amount, deposit.Data, blockNumber) + + s.Nil(message) + s.NotNil(err) +} diff --git a/chains/btc/listener/event-handlers.go b/chains/btc/listener/event-handlers.go new file mode 100644 index 00000000..925c0ebc --- /dev/null +++ b/chains/btc/listener/event-handlers.go @@ -0,0 +1,142 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package listener + +import ( + "math/big" + "strconv" + + "github.com/ChainSafe/sygma-relayer/chains/btc/config" + "github.com/btcsuite/btcd/btcjson" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/sygmaprotocol/sygma-core/relayer/message" +) + +type Deposit struct { + // ResourceID used to find address of handler to be used for deposit + ResourceID [32]byte + // Address of sender (msg.sender: user) + SenderAddress string + // Additional data to be passed to specified handler + Amount *big.Int + Data string +} + +type DepositHandler interface { + HandleDeposit( + sourceID uint8, + depositNonce uint64, + resourceID [32]byte, + amount *big.Int, + data string, + blockNumber *big.Int, + ) (*message.Message, error) +} + +type FungibleTransferEventHandler struct { + depositHandler DepositHandler + domainID uint8 + log zerolog.Logger + conn Connection + msgChan chan []*message.Message + resource config.Resource +} + +func NewFungibleTransferEventHandler(logC zerolog.Context, domainID uint8, depositHandler DepositHandler, msgChan chan []*message.Message, conn Connection, resource config.Resource) *FungibleTransferEventHandler { + return &FungibleTransferEventHandler{ + depositHandler: depositHandler, + domainID: domainID, + log: logC.Logger(), + conn: conn, + msgChan: msgChan, + resource: resource, + } +} + +func (eh *FungibleTransferEventHandler) HandleEvents(blockNumber *big.Int) error { + domainDeposits := make(map[uint8][]*message.Message) + evts, err := eh.FetchEvents(blockNumber) + if err != nil { + eh.log.Error().Err(err).Msg("Error fetching events") + return err + } + for evtNumber, evt := range evts { + err := func(evt btcjson.TxRawResult) error { + defer func() { + if r := recover(); r != nil { + log.Error().Msgf("panic occured while handling deposit %+v", evt) + } + }() + + d, isDeposit, err := DecodeDepositEvent(evt, eh.resource) + if err != nil { + return err + } + + if !isDeposit { + return nil + } + nonce, err := eh.CalculateNonce(blockNumber, evtNumber) + if err != nil { + return err + } + + m, err := eh.depositHandler.HandleDeposit(eh.domainID, nonce, d.ResourceID, d.Amount, d.Data, blockNumber) + if err != nil { + return err + } + + log.Debug().Str("messageID", m.ID).Msgf("Resolved message %+v in block: %s", m, blockNumber.String()) + domainDeposits[m.Destination] = append(domainDeposits[m.Destination], m) + return nil + }(evt) + if err != nil { + log.Error().Err(err).Msgf("%v", err) + } + } + + for _, deposits := range domainDeposits { + go func(d []*message.Message) { + eh.msgChan <- d + }(deposits) + } + return nil +} + +func (eh *FungibleTransferEventHandler) FetchEvents(startBlock *big.Int) ([]btcjson.TxRawResult, error) { + blockHash, err := eh.conn.GetBlockHash(startBlock.Int64()) + if err != nil { + return nil, err + } + + // Fetch block details in verbose mode + block, err := eh.conn.GetBlockVerboseTx(blockHash) + if err != nil { + return nil, err + } + return block.Tx, nil +} + +func (eh *FungibleTransferEventHandler) CalculateNonce(blockNumber *big.Int, evtNumber int) (uint64, error) { + // Convert blockNumber to string + blockNumberStr := blockNumber.String() + + // Convert evtNumber to *big.Int + evtNumberBigInt := big.NewInt(int64(evtNumber)) + + // Convert evtNumberBigInt to string + evtNumberStr := evtNumberBigInt.String() + + // Concatenate blockNumberStr and evtNumberStr + concatenatedStr := blockNumberStr + evtNumberStr + + // Parse the concatenated string to uint64 + result, err := strconv.ParseUint(concatenatedStr, 10, 64) + if err != nil { + return 0, err + } + + return result, nil +} diff --git a/chains/btc/listener/event-handlers_test.go b/chains/btc/listener/event-handlers_test.go new file mode 100644 index 00000000..e408411e --- /dev/null +++ b/chains/btc/listener/event-handlers_test.go @@ -0,0 +1,186 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package listener_test + +import ( + "fmt" + "math/big" + "strings" + + "github.com/ChainSafe/sygma-relayer/chains/btc/config" + "github.com/ChainSafe/sygma-relayer/chains/btc/listener" + mock_listener "github.com/ChainSafe/sygma-relayer/chains/btc/listener/mock" + "github.com/ChainSafe/sygma-relayer/relayer/transfer" + "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/ethereum/go-ethereum/common" + "github.com/rs/zerolog" + "github.com/sygmaprotocol/sygma-core/relayer/message" + + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/suite" +) + +type DepositHandlerTestSuite struct { + suite.Suite + fungibleTransferEventHandler *listener.FungibleTransferEventHandler + mockDepositHandler *mock_listener.MockDepositHandler + domainID uint8 + resource config.Resource + msgChan chan []*message.Message + mockConn *mock_listener.MockConnection +} + +func TestRunDepositHandlerTestSuite(t *testing.T) { + suite.Run(t, new(DepositHandlerTestSuite)) +} + +func (s *DepositHandlerTestSuite) SetupTest() { + ctrl := gomock.NewController(s.T()) + s.domainID = 1 + address, _ := btcutil.DecodeAddress("tb1qln69zuhdunc9stwfh6t7adexxrcr04ppy6thgm", &chaincfg.TestNet3Params) + s.resource = config.Resource{Address: address, ResourceID: [32]byte{}} + s.mockDepositHandler = mock_listener.NewMockDepositHandler(ctrl) + s.msgChan = make(chan []*message.Message, 2) + s.mockConn = mock_listener.NewMockConnection(ctrl) + s.fungibleTransferEventHandler = listener.NewFungibleTransferEventHandler(zerolog.Context{}, s.domainID, s.mockDepositHandler, s.msgChan, s.mockConn, s.resource) +} + +func (s *DepositHandlerTestSuite) Test_FetchDepositFails_GetBlockHashError() { + s.mockConn.EXPECT().GetBlockHash(int64(100)).Return(nil, fmt.Errorf("error")) + err := s.fungibleTransferEventHandler.HandleEvents(big.NewInt(100)) + + s.NotNil(err) +} + +func (s *DepositHandlerTestSuite) Test_FetchDepositFails_GetBlockVerboseTxError() { + hash, _ := chainhash.NewHashFromStr("00000000000000000008bba5a6ff31fdb9bb1d4147905b5b3c47a07a07235bfc") + s.mockConn.EXPECT().GetBlockHash(int64(100)).Return(hash, nil) + s.mockConn.EXPECT().GetBlockVerboseTx(hash).Return(nil, fmt.Errorf("error")) + + err := s.fungibleTransferEventHandler.HandleEvents(big.NewInt(100)) + s.NotNil(err) +} + +func (s *DepositHandlerTestSuite) Test_CalculateNonceFail_BlockNumberOverflow() { + + blockNumber := new(big.Int) + blockNumber.SetString("18446744073709551616", 10) + nonce, err := s.fungibleTransferEventHandler.CalculateNonce(blockNumber, 5) + s.Equal(nonce, uint64(0)) + s.NotNil(err) +} + +func (s *DepositHandlerTestSuite) Test_CalculateNonce() { + blockNumber := big.NewInt(123) + nonce, err := s.fungibleTransferEventHandler.CalculateNonce(blockNumber, 4) + s.Equal(nonce, uint64(1234)) + s.Nil(err) +} + +func (s *DepositHandlerTestSuite) Test_HandleDepositFails_ExecutionContinue() { + blockNumber := big.NewInt(100) + data2 := map[string]any{ + "deposit_nonce": uint64(1001), + "resource_id": [32]byte{0}, + "amount": big.NewInt(19000), + "deposit_data": "0xe9f23A8289764280697a03aC06795eA92a170e42_1", + } + + dat := strings.Split("0xe9f23A8289764280697a03aC06795eA92a170e42_1", "_") + evmAdd := common.HexToAddress(dat[0]).Bytes() + s.mockDepositHandler.EXPECT().HandleDeposit( + s.domainID, + data2["deposit_nonce"], + data2["resource_id"], + data2["amount"], + data2["deposit_data"], + blockNumber, + ).Return(&message.Message{ + Source: s.domainID, + Destination: uint8(1), + Data: []interface{}{ + big.NewInt(19000).Bytes(), + evmAdd, + }, + Type: transfer.TransferMessageType, + ID: "messageid", + }, nil) + + d2 := btcjson.TxRawResult{ + Vin: []btcjson.Vin{ + + { + Txid: "00000000000000000008bba5a6ff31fdb9bb1d4147905b5b3c47a07a07235bfc", + }, + }, + Vout: []btcjson.Vout{ + { + ScriptPubKey: btcjson.ScriptPubKeyResult{ + Type: "nulldata", + Hex: "6a2c3078653966323341383238393736343238303639376130336143303637393565413932613137306534325f31", + }, + }, + { + ScriptPubKey: btcjson.ScriptPubKeyResult{ + Type: "witness_v1_taproot", + Address: "tb1qln69zuhdunc9stwfh6t7adexxrcr04ppy6thgm", + }, + Value: float64(0.00019), + }, + }, + } + + d1 := btcjson.TxRawResult{ + Vin: []btcjson.Vin{ + + { + Txid: "00000000000000000008bba5a6ff31fdb9bb1d4147905b5b3c47a07a07235bfc", + }, + }, + Vout: []btcjson.Vout{ + { + ScriptPubKey: btcjson.ScriptPubKeyResult{ + Type: "nulldata", + Hex: "6a2c3078653966323341383238393736343238303639376130336143303637393565413932613137306534325f31", + }, + }, + { + ScriptPubKey: btcjson.ScriptPubKeyResult{ + Type: "witness_v1_taproot", + Address: "invalidBridgeAddressuhdunc9stwfh6t7adexxrcr04ppy6thgm", + }, + Value: float64(0.00019), + }, + }, + } + + evts := []btcjson.TxRawResult{d1, d2} + hash, _ := chainhash.NewHashFromStr("00000000000000000008bba5a6ff31fdb9bb1d4147905b5b3c47a07a07235bfc") + sampleResult := &btcjson.GetBlockVerboseTxResult{ + Hash: "00000000000000000008bba5a6ff31fdb9bb1d4147905b5b3c47a07a07235bfc", + Height: 100, + Tx: evts, + } + + s.mockConn.EXPECT().GetBlockHash(int64(100)).Return(hash, nil) + s.mockConn.EXPECT().GetBlockVerboseTx(hash).Return(sampleResult, nil) + + err := s.fungibleTransferEventHandler.HandleEvents(blockNumber) + msgs := <-s.msgChan + + s.Nil(err) + s.Equal(msgs, []*message.Message{{Source: s.domainID, + Destination: 1, + Data: []interface{}{ + big.NewInt(19000).Bytes(), + evmAdd, + }, + Type: transfer.TransferMessageType, + ID: "messageid"}}) +} diff --git a/chains/btc/listener/listener.go b/chains/btc/listener/listener.go new file mode 100644 index 00000000..f2123315 --- /dev/null +++ b/chains/btc/listener/listener.go @@ -0,0 +1,111 @@ +// Copyright 2021 ChainSafe Systems +// SPDX-License-Identifier: LGPL-3.0-only + +package listener + +import ( + "context" + "math/big" + "time" + + "github.com/ChainSafe/sygma-relayer/chains/btc/config" + "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +type EventHandler interface { + HandleEvents(startBlock *big.Int) error +} +type BlockStorer interface { + StoreBlock(block *big.Int, domainID uint8) error +} +type Connection interface { + GetRawTransactionVerbose(*chainhash.Hash) (*btcjson.TxRawResult, error) + GetBlockHash(int64) (*chainhash.Hash, error) + GetBlockVerboseTx(*chainhash.Hash) (*btcjson.GetBlockVerboseTxResult, error) + GetBestBlockHash() (*chainhash.Hash, error) +} +type BtcListener struct { + conn Connection + + eventHandlers []EventHandler + blockRetryInterval time.Duration + blockConfirmations *big.Int + blockstore BlockStorer + + log zerolog.Logger + domainID uint8 +} + +// NewBtcListener creates an BtcListener that listens to deposit events on chain +// and calls event handler when one occurs +func NewBtcListener(connection Connection, eventHandlers []EventHandler, config *config.BtcConfig, blockstore BlockStorer, +) *BtcListener { + return &BtcListener{ + log: log.With().Uint8("domainID", *config.GeneralChainConfig.Id).Logger(), + conn: connection, + eventHandlers: eventHandlers, + blockRetryInterval: config.BlockRetryInterval, + blockConfirmations: config.BlockConfirmations, + blockstore: blockstore, + domainID: *config.GeneralChainConfig.Id, + } +} + +// ListenToEvents goes block by block of a network and executes event handlers that are +// configured for the listener. +func (l *BtcListener) ListenToEvents(ctx context.Context, startBlock *big.Int) { +loop: + for { + select { + case <-ctx.Done(): + return + default: + // Get the hash of the most recent block + bestBlockHash, err := l.conn.GetBestBlockHash() + if err != nil { + l.log.Warn().Err(err).Msg("Unable to get latest block") + time.Sleep(l.blockRetryInterval) + continue + } + + // Fetch the most recent block in verbose mode to get additional information including height + block, err := l.conn.GetBlockVerboseTx(bestBlockHash) + if err != nil { + l.log.Warn().Err(err).Msg("Unable to get latest block") + time.Sleep(l.blockRetryInterval) + continue + } + + head := big.NewInt(block.Height) + if startBlock == nil { + startBlock = head + } + + // Sleep if startBlock is higher then head + if new(big.Int).Sub(head, startBlock).Cmp(l.blockConfirmations) == -1 { + time.Sleep(l.blockRetryInterval) + continue + } + + log.Debug().Msgf("Fetching btc events for block %d", startBlock) + + for _, handler := range l.eventHandlers { + err := handler.HandleEvents(startBlock) + if err != nil { + l.log.Warn().Err(err).Msgf("Unable to handle events") + continue loop + } + } + + //Write to block store. Not a critical operation, no need to retry + err = l.blockstore.StoreBlock(startBlock, l.domainID) + if err != nil { + l.log.Error().Str("block", startBlock.String()).Err(err).Msg("Failed to write latest block to blockstore") + } + startBlock.Add(startBlock, big.NewInt(1)) + } + } +} diff --git a/chains/btc/listener/listener_test.go b/chains/btc/listener/listener_test.go new file mode 100644 index 00000000..bc95228b --- /dev/null +++ b/chains/btc/listener/listener_test.go @@ -0,0 +1,145 @@ +package listener_test + +import ( + "context" + "fmt" + "math/big" + "testing" + "time" + + "github.com/ChainSafe/sygma-relayer/chains/btc/config" + "github.com/ChainSafe/sygma-relayer/chains/btc/listener" + mock_listener "github.com/ChainSafe/sygma-relayer/chains/btc/listener/mock" + "github.com/ChainSafe/sygma-relayer/config/chain" + "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/suite" +) + +type ListenerTestSuite struct { + suite.Suite + listener *listener.BtcListener + mockConn *mock_listener.MockConnection + mockEventHandler *mock_listener.MockEventHandler + mockBlockStorer *mock_listener.MockBlockStorer + domainID uint8 +} + +func TestRunTestSuite(t *testing.T) { + suite.Run(t, new(ListenerTestSuite)) +} + +func (s *ListenerTestSuite) SetupTest() { + s.domainID = 1 + btcConfig := config.BtcConfig{ + GeneralChainConfig: chain.GeneralChainConfig{ + Id: &s.domainID, + }, + BlockRetryInterval: time.Millisecond * 75, + BlockConfirmations: big.NewInt(5), + } + + ctrl := gomock.NewController(s.T()) + s.mockBlockStorer = mock_listener.NewMockBlockStorer(ctrl) + + s.mockConn = mock_listener.NewMockConnection(ctrl) + s.mockEventHandler = mock_listener.NewMockEventHandler(ctrl) + + s.listener = listener.NewBtcListener( + s.mockConn, + []listener.EventHandler{s.mockEventHandler, s.mockEventHandler}, + &btcConfig, + s.mockBlockStorer, + ) +} + +func (s *ListenerTestSuite) Test_ListenToEvents_RetriesIfFinalizedHeadUnavailable() { + s.mockConn.EXPECT().GetBestBlockHash().Return(nil, fmt.Errorf("error")) + + ctx, cancel := context.WithCancel(context.Background()) + go s.listener.ListenToEvents(ctx, big.NewInt(100)) + + time.Sleep(time.Millisecond * 50) + cancel() + +} + +func (s *ListenerTestSuite) Test_ListenToEvents_GetVerboseTxError() { + hash, _ := chainhash.NewHashFromStr("00000000000000000008bba5a6ff31fdb9bb1d4147905b5b3c47a07a07235bfc") + s.mockConn.EXPECT().GetBestBlockHash().Return(hash, nil) + s.mockConn.EXPECT().GetBlockVerboseTx(hash).Return(nil, fmt.Errorf("error")) + + ctx, cancel := context.WithCancel(context.Background()) + go s.listener.ListenToEvents(ctx, big.NewInt(100)) + + time.Sleep(time.Millisecond * 50) + cancel() +} + +func (s *ListenerTestSuite) Test_ListenToEvents_SleepsIfBlockTooNew() { + hash, _ := chainhash.NewHashFromStr("00000000000000000008bba5a6ff31fdb9bb1d4147905b5b3c47a07a07235bfc") + s.mockConn.EXPECT().GetBestBlockHash().Return(hash, nil) + s.mockConn.EXPECT().GetBlockVerboseTx(hash).Return(&btcjson.GetBlockVerboseTxResult{Height: int64(102)}, nil) + + ctx, cancel := context.WithCancel(context.Background()) + go s.listener.ListenToEvents(ctx, big.NewInt(100)) + + time.Sleep(time.Millisecond * 50) + cancel() +} + +func (s *ListenerTestSuite) Test_ListenToEvents_RetriesInCaseOfHandlerFailure() { + startBlock := big.NewInt(105) + head := int64(110) + + // First pass + hash, _ := chainhash.NewHashFromStr("00000000000000000008bba5a6ff31fdb9bb1d4147905b5b3c47a07a07235bfc") + s.mockConn.EXPECT().GetBestBlockHash().Return(hash, nil) + s.mockConn.EXPECT().GetBlockVerboseTx(hash).Return(&btcjson.GetBlockVerboseTxResult{Height: head}, nil) + s.mockEventHandler.EXPECT().HandleEvents(startBlock).Return(fmt.Errorf("error")) + // Second pass + s.mockConn.EXPECT().GetBestBlockHash().Return(hash, nil) + s.mockConn.EXPECT().GetBlockVerboseTx(hash).Return(&btcjson.GetBlockVerboseTxResult{Height: head}, nil) + s.mockEventHandler.EXPECT().HandleEvents(startBlock).Return(nil) + s.mockEventHandler.EXPECT().HandleEvents(startBlock).Return(nil) + + s.mockBlockStorer.EXPECT().StoreBlock(startBlock, s.domainID).Return(nil) + // third pass + s.mockConn.EXPECT().GetBestBlockHash().Return(hash, nil) + s.mockConn.EXPECT().GetBlockVerboseTx(hash).Return(&btcjson.GetBlockVerboseTxResult{Height: head}, nil) + + ctx, cancel := context.WithCancel(context.Background()) + + go s.listener.ListenToEvents(ctx, big.NewInt(105)) + + time.Sleep(time.Millisecond * 50) + cancel() +} + +func (s *ListenerTestSuite) Test_ListenToEvents_UsesHeadAsStartBlockIfNilPassed() { + startBlock := big.NewInt(100) + oldHead := int64(100) + newHead := int64(106) + hash, _ := chainhash.NewHashFromStr("00000000000000000008bba5a6ff31fdb9bb1d4147905b5b3c47a07a07235bfc") + s.mockConn.EXPECT().GetBestBlockHash().Return(hash, nil) + s.mockConn.EXPECT().GetBlockVerboseTx(hash).Return(&btcjson.GetBlockVerboseTxResult{Height: oldHead}, nil) + + s.mockConn.EXPECT().GetBestBlockHash().Return(hash, nil) + s.mockConn.EXPECT().GetBlockVerboseTx(hash).Return(&btcjson.GetBlockVerboseTxResult{Height: newHead}, nil) + + s.mockConn.EXPECT().GetBestBlockHash().Return(hash, nil) + s.mockConn.EXPECT().GetBlockVerboseTx(hash).Return(&btcjson.GetBlockVerboseTxResult{Height: int64(50)}, nil) + + s.mockEventHandler.EXPECT().HandleEvents(startBlock).Return(nil) + s.mockEventHandler.EXPECT().HandleEvents(startBlock).Return(nil) + + s.mockBlockStorer.EXPECT().StoreBlock(startBlock, s.domainID).Return(nil) + + ctx, cancel := context.WithCancel(context.Background()) + + go s.listener.ListenToEvents(ctx, nil) + + time.Sleep(time.Millisecond * 100) + cancel() +} diff --git a/chains/btc/listener/mock/handlers.go b/chains/btc/listener/mock/handlers.go new file mode 100644 index 00000000..e685c0c0 --- /dev/null +++ b/chains/btc/listener/mock/handlers.go @@ -0,0 +1,51 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./chains/btc/listener/event-handlers.go + +// Package mock_listener is a generated GoMock package. +package mock_listener + +import ( + big "math/big" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + message "github.com/sygmaprotocol/sygma-core/relayer/message" +) + +// MockDepositHandler is a mock of DepositHandler interface. +type MockDepositHandler struct { + ctrl *gomock.Controller + recorder *MockDepositHandlerMockRecorder +} + +// MockDepositHandlerMockRecorder is the mock recorder for MockDepositHandler. +type MockDepositHandlerMockRecorder struct { + mock *MockDepositHandler +} + +// NewMockDepositHandler creates a new mock instance. +func NewMockDepositHandler(ctrl *gomock.Controller) *MockDepositHandler { + mock := &MockDepositHandler{ctrl: ctrl} + mock.recorder = &MockDepositHandlerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDepositHandler) EXPECT() *MockDepositHandlerMockRecorder { + return m.recorder +} + +// HandleDeposit mocks base method. +func (m *MockDepositHandler) HandleDeposit(sourceID uint8, depositNonce uint64, resourceID [32]byte, amount *big.Int, data string, blockNumber *big.Int) (*message.Message, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HandleDeposit", sourceID, depositNonce, resourceID, amount, data, blockNumber) + ret0, _ := ret[0].(*message.Message) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HandleDeposit indicates an expected call of HandleDeposit. +func (mr *MockDepositHandlerMockRecorder) HandleDeposit(sourceID, depositNonce, resourceID, amount, data, blockNumber interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleDeposit", reflect.TypeOf((*MockDepositHandler)(nil).HandleDeposit), sourceID, depositNonce, resourceID, amount, data, blockNumber) +} diff --git a/chains/btc/listener/mock/listener.go b/chains/btc/listener/mock/listener.go new file mode 100644 index 00000000..bef0280e --- /dev/null +++ b/chains/btc/listener/mock/listener.go @@ -0,0 +1,171 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./chains/btc/listener/listener.go + +// Package mock_listener is a generated GoMock package. +package mock_listener + +import ( + big "math/big" + reflect "reflect" + + btcjson "github.com/btcsuite/btcd/btcjson" + chainhash "github.com/btcsuite/btcd/chaincfg/chainhash" + gomock "github.com/golang/mock/gomock" +) + +// MockEventHandler is a mock of EventHandler interface. +type MockEventHandler struct { + ctrl *gomock.Controller + recorder *MockEventHandlerMockRecorder +} + +// MockEventHandlerMockRecorder is the mock recorder for MockEventHandler. +type MockEventHandlerMockRecorder struct { + mock *MockEventHandler +} + +// NewMockEventHandler creates a new mock instance. +func NewMockEventHandler(ctrl *gomock.Controller) *MockEventHandler { + mock := &MockEventHandler{ctrl: ctrl} + mock.recorder = &MockEventHandlerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockEventHandler) EXPECT() *MockEventHandlerMockRecorder { + return m.recorder +} + +// HandleEvents mocks base method. +func (m *MockEventHandler) HandleEvents(startBlock *big.Int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HandleEvents", startBlock) + ret0, _ := ret[0].(error) + return ret0 +} + +// HandleEvents indicates an expected call of HandleEvents. +func (mr *MockEventHandlerMockRecorder) HandleEvents(startBlock interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleEvents", reflect.TypeOf((*MockEventHandler)(nil).HandleEvents), startBlock) +} + +// MockBlockStorer is a mock of BlockStorer interface. +type MockBlockStorer struct { + ctrl *gomock.Controller + recorder *MockBlockStorerMockRecorder +} + +// MockBlockStorerMockRecorder is the mock recorder for MockBlockStorer. +type MockBlockStorerMockRecorder struct { + mock *MockBlockStorer +} + +// NewMockBlockStorer creates a new mock instance. +func NewMockBlockStorer(ctrl *gomock.Controller) *MockBlockStorer { + mock := &MockBlockStorer{ctrl: ctrl} + mock.recorder = &MockBlockStorerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBlockStorer) EXPECT() *MockBlockStorerMockRecorder { + return m.recorder +} + +// StoreBlock mocks base method. +func (m *MockBlockStorer) StoreBlock(block *big.Int, domainID uint8) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StoreBlock", block, domainID) + ret0, _ := ret[0].(error) + return ret0 +} + +// StoreBlock indicates an expected call of StoreBlock. +func (mr *MockBlockStorerMockRecorder) StoreBlock(block, domainID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StoreBlock", reflect.TypeOf((*MockBlockStorer)(nil).StoreBlock), block, domainID) +} + +// MockConnection is a mock of Connection interface. +type MockConnection struct { + ctrl *gomock.Controller + recorder *MockConnectionMockRecorder +} + +// MockConnectionMockRecorder is the mock recorder for MockConnection. +type MockConnectionMockRecorder struct { + mock *MockConnection +} + +// NewMockConnection creates a new mock instance. +func NewMockConnection(ctrl *gomock.Controller) *MockConnection { + mock := &MockConnection{ctrl: ctrl} + mock.recorder = &MockConnectionMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockConnection) EXPECT() *MockConnectionMockRecorder { + return m.recorder +} + +// GetBestBlockHash mocks base method. +func (m *MockConnection) GetBestBlockHash() (*chainhash.Hash, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBestBlockHash") + ret0, _ := ret[0].(*chainhash.Hash) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBestBlockHash indicates an expected call of GetBestBlockHash. +func (mr *MockConnectionMockRecorder) GetBestBlockHash() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBestBlockHash", reflect.TypeOf((*MockConnection)(nil).GetBestBlockHash)) +} + +// GetBlockHash mocks base method. +func (m *MockConnection) GetBlockHash(arg0 int64) (*chainhash.Hash, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBlockHash", arg0) + ret0, _ := ret[0].(*chainhash.Hash) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBlockHash indicates an expected call of GetBlockHash. +func (mr *MockConnectionMockRecorder) GetBlockHash(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockHash", reflect.TypeOf((*MockConnection)(nil).GetBlockHash), arg0) +} + +// GetBlockVerboseTx mocks base method. +func (m *MockConnection) GetBlockVerboseTx(arg0 *chainhash.Hash) (*btcjson.GetBlockVerboseTxResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBlockVerboseTx", arg0) + ret0, _ := ret[0].(*btcjson.GetBlockVerboseTxResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBlockVerboseTx indicates an expected call of GetBlockVerboseTx. +func (mr *MockConnectionMockRecorder) GetBlockVerboseTx(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockVerboseTx", reflect.TypeOf((*MockConnection)(nil).GetBlockVerboseTx), arg0) +} + +// GetRawTransactionVerbose mocks base method. +func (m *MockConnection) GetRawTransactionVerbose(arg0 *chainhash.Hash) (*btcjson.TxRawResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRawTransactionVerbose", arg0) + ret0, _ := ret[0].(*btcjson.TxRawResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRawTransactionVerbose indicates an expected call of GetRawTransactionVerbose. +func (mr *MockConnectionMockRecorder) GetRawTransactionVerbose(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRawTransactionVerbose", reflect.TypeOf((*MockConnection)(nil).GetRawTransactionVerbose), arg0) +} diff --git a/chains/btc/listener/util.go b/chains/btc/listener/util.go new file mode 100644 index 00000000..ab4012f8 --- /dev/null +++ b/chains/btc/listener/util.go @@ -0,0 +1,58 @@ +package listener + +import ( + "encoding/hex" + "math/big" + + "github.com/ChainSafe/sygma-relayer/chains/btc/config" + "github.com/btcsuite/btcd/btcjson" +) + +const ( + WitnessV1Taproot = "witness_v1_taproot" + OP_RETURN = "nulldata" +) + +func DecodeDepositEvent(evt btcjson.TxRawResult, resource config.Resource) (Deposit, bool, error) { + amount := big.NewInt(0) + isBridgeDeposit := false + sender := "" + data := "" + resourceID := [32]byte{} + for _, vout := range evt.Vout { + // read the OP_RETURN data + if vout.ScriptPubKey.Type == OP_RETURN { + opReturnData, err := hex.DecodeString(vout.ScriptPubKey.Hex) + if err != nil { + return Deposit{}, true, err + } + // Extract OP_RETURN data (excluding OP_RETURN prefix) + data = string(opReturnData[2:]) + } + + if resource.Address.String() == vout.ScriptPubKey.Address { + isBridgeDeposit = true + resourceID = resource.ResourceID + if vout.ScriptPubKey.Type == WitnessV1Taproot { + amount.Add(amount, big.NewInt(int64(vout.Value*1e8))) + } + } + } + + if !isBridgeDeposit { + return Deposit{}, false, nil + } + + return Deposit{ + ResourceID: resourceID, + SenderAddress: sender, + Amount: amount, + Data: data, + }, true, nil +} + +func SliceTo32Bytes(in []byte) [32]byte { + var res [32]byte + copy(res[:], in) + return res +} diff --git a/chains/btc/listener/util_test.go b/chains/btc/listener/util_test.go new file mode 100644 index 00000000..25aba540 --- /dev/null +++ b/chains/btc/listener/util_test.go @@ -0,0 +1,127 @@ +package listener_test + +import ( + "math/big" + "testing" + + "github.com/ChainSafe/sygma-relayer/chains/btc/config" + "github.com/ChainSafe/sygma-relayer/chains/btc/listener" + mock_listener "github.com/ChainSafe/sygma-relayer/chains/btc/listener/mock" + "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/suite" +) + +type DecodeEventsSuite struct { + suite.Suite + mockConn *mock_listener.MockConnection + resource config.Resource +} + +func TestRunDecodeDepositEventsSuite(t *testing.T) { + suite.Run(t, new(DecodeEventsSuite)) +} + +func (s *DecodeEventsSuite) SetupTest() { + ctrl := gomock.NewController(s.T()) + address, _ := btcutil.DecodeAddress("tb1qln69zuhdunc9stwfh6t7adexxrcr04ppy6thgm", &chaincfg.TestNet3Params) + s.resource = config.Resource{Address: address, ResourceID: [32]byte{}} + s.mockConn = mock_listener.NewMockConnection(ctrl) +} + +func (s *DecodeEventsSuite) Test_DecodeDepositEvent_ErrorDecodingOPRETURNData() { + d1 := btcjson.TxRawResult{ + Vin: []btcjson.Vin{ + + { + Txid: "00000000000000000008bba5a6ff31fdb9bb1d4147905b5b3c47a07a07235bfc", + }, + }, + Vout: []btcjson.Vout{ + { + ScriptPubKey: btcjson.ScriptPubKeyResult{ + Type: "nulldata", + Hex: "InvalidCharć", + }, + }, + { + ScriptPubKey: btcjson.ScriptPubKeyResult{ + Type: "witness_v1_taproot", + Address: "tb1qln69zuhdunc9stwfh6t7adexxrcr04ppy6thgm", + }, + Value: float64(0.00019), + }, + }, + } + + deposit, isDeposit, err := listener.DecodeDepositEvent(d1, s.resource) + s.Equal(isDeposit, true) + s.NotNil(err) + s.Equal(deposit, listener.Deposit{}) +} + +func (s *DecodeEventsSuite) Test_DecodeDepositEvent() { + d1 := btcjson.TxRawResult{ + Vin: []btcjson.Vin{ + + { + Txid: "00000000000000000008bba5a6ff31fdb9bb1d4147905b5b3c47a07a07235bfc", + }, + }, + Vout: []btcjson.Vout{ + { + ScriptPubKey: btcjson.ScriptPubKeyResult{ + Type: "nulldata", + Hex: "6a2c3078653966323341383238393736343238303639376130336143303637393565413932613137306534325f31", + }, + }, + { + ScriptPubKey: btcjson.ScriptPubKeyResult{ + Type: "witness_v1_taproot", + Address: "tb1qln69zuhdunc9stwfh6t7adexxrcr04ppy6thgm", + }, + Value: float64(0.00019), + }, + }, + } + deposit, isDeposit, err := listener.DecodeDepositEvent(d1, s.resource) + s.Equal(isDeposit, true) + s.Nil(err) + s.Equal(deposit, listener.Deposit{ + ResourceID: [32]byte{}, + Amount: big.NewInt(int64(d1.Vout[1].Value * 1e8)), + Data: "0xe9f23A8289764280697a03aC06795eA92a170e42_1", + }) +} + +func (s *DecodeEventsSuite) Test_DecodeDepositEvent_NotBridgeDepositTx() { + d1 := btcjson.TxRawResult{ + Vin: []btcjson.Vin{ + + { + Txid: "00000000000000000008bba5a6ff31fdb9bb1d4147905b5b3c47a07a07235bfc", + }, + }, + Vout: []btcjson.Vout{ + { + ScriptPubKey: btcjson.ScriptPubKeyResult{ + Type: "nulldata", + Hex: "6a2c3078653966323341383238393736343238303639376130336143303637393565413932613137306534325f31", + }, + }, + { + ScriptPubKey: btcjson.ScriptPubKeyResult{ + Type: "witness_v1_taproot", + Address: "NotBridgeAddress", + }, + Value: float64(0.00019), + }, + }, + } + deposit, isDeposit, err := listener.DecodeDepositEvent(d1, s.resource) + s.Equal(isDeposit, false) + s.Nil(err) + s.Equal(deposit, listener.Deposit{}) +} diff --git a/chains/btc/mempool/mempool.go b/chains/btc/mempool/mempool.go new file mode 100644 index 00000000..6692e171 --- /dev/null +++ b/chains/btc/mempool/mempool.go @@ -0,0 +1,76 @@ +package mempool + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "sort" +) + +type Utxo struct { + TxID string `json:"txid"` + Vout uint32 `json:"vout"` + Value uint64 `json:"value"` +} + +type Fee struct { + FastestFee int64 + HalfHourFee int64 + MinimumFee int64 + EconomyFee int64 + HourFee int64 +} + +type MempoolAPI struct { + url string +} + +func NewMempoolAPI(url string) *MempoolAPI { + return &MempoolAPI{ + url: url, + } +} + +func (a *MempoolAPI) RecommendedFee() (*Fee, error) { + resp, err := http.Get(fmt.Sprintf("%s/api/v1/fees/recommended", a.url)) + if err != nil { + return nil, err + } + + var fee *Fee + defer resp.Body.Close() + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + err = json.Unmarshal(data, &fee) + if err != nil { + return nil, err + } + + return fee, nil +} + +func (a *MempoolAPI) Utxos(address string) ([]Utxo, error) { + resp, err := http.Get(fmt.Sprintf("%s/api/address/%s/utxo", a.url, address)) + if err != nil { + return nil, err + } + + var utxos []Utxo + defer resp.Body.Close() + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + err = json.Unmarshal(data, &utxos) + if err != nil { + return nil, err + } + sort.Slice(utxos, func(i int, j int) bool { + return utxos[i].TxID < utxos[j].TxID + }) + + return utxos, nil +} diff --git a/chains/btc/mempool/mempool_test.go b/chains/btc/mempool/mempool_test.go new file mode 100644 index 00000000..b6aecc4c --- /dev/null +++ b/chains/btc/mempool/mempool_test.go @@ -0,0 +1,83 @@ +package mempool_test + +import ( + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/ChainSafe/sygma-relayer/chains/btc/mempool" + "github.com/stretchr/testify/suite" +) + +func jsonFileToBytes(filename string) []byte { + file, _ := os.ReadFile(filename) + return file +} + +type MempoolTestSuite struct { + suite.Suite + mempoolAPI *mempool.MempoolAPI + server *httptest.Server +} + +func TestMempoolTestSuite(t *testing.T) { + suite.Run(t, new(MempoolTestSuite)) +} + +func (s *MempoolTestSuite) SetupTest() { + s.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/api/address/testaddress/utxo" { + w.WriteHeader(http.StatusOK) + _, _ = w.Write(jsonFileToBytes("./test-data/successful-utxo.json")) + } else if r.URL.Path == "/api/v1/fees/recommended" { + w.WriteHeader(http.StatusOK) + _, _ = w.Write(jsonFileToBytes("./test-data/successful-fee.json")) + } else { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte("{\"status\":\"Not found\"}")) + } + })) + s.mempoolAPI = mempool.NewMempoolAPI(s.server.URL) +} +func (s *MempoolTestSuite) TeardownTest() { + s.server.Close() +} + +func (s *MempoolTestSuite) Test_Utxo_SuccessfulFetch() { + utxos, err := s.mempoolAPI.Utxos("testaddress") + + s.Nil(err) + s.Equal(utxos, []mempool.Utxo{ + { + TxID: "28154e2008912d27978225c096c22ffe2ea65e1d55bf440ee41c21f9489c7fe1", + Vout: 0, + Value: 11198, + }, + { + TxID: "28154e2008912d27978225c096c22ffe2ea65e1d55bf440ee41c21f9489c7fe9", + Vout: 0, + Value: 11197, + }, + }) +} + +func (s *MempoolTestSuite) Test_Utxo_NotFound() { + _, err := s.mempoolAPI.Utxos("invalid") + + s.NotNil(err) +} + +func (s *MempoolTestSuite) Test_RecommendedFee_SuccessfulFetch() { + recommendedFee, err := s.mempoolAPI.RecommendedFee() + + s.Nil(err) + s.Equal(recommendedFee, &mempool.Fee{ + FastestFee: 1, + HalfHourFee: 2, + HourFee: 3, + EconomyFee: 4, + MinimumFee: 5, + }) + +} diff --git a/chains/btc/mempool/test-data/successful-fee.json b/chains/btc/mempool/test-data/successful-fee.json new file mode 100644 index 00000000..6eb1d675 --- /dev/null +++ b/chains/btc/mempool/test-data/successful-fee.json @@ -0,0 +1,7 @@ +{ + "fastestFee": 1, + "halfHourFee": 2, + "hourFee": 3, + "economyFee": 4, + "minimumFee": 5 +} \ No newline at end of file diff --git a/chains/btc/mempool/test-data/successful-utxo.json b/chains/btc/mempool/test-data/successful-utxo.json new file mode 100644 index 00000000..4ae65107 --- /dev/null +++ b/chains/btc/mempool/test-data/successful-utxo.json @@ -0,0 +1,4 @@ +[ + {"txid":"28154e2008912d27978225c096c22ffe2ea65e1d55bf440ee41c21f9489c7fe9","vout":0,"status":{"confirmed":true,"block_height":2812826,"block_hash":"000000000000001a01d4058773384f2c23aed5a7e5ede252f99e290fa58324a3","block_time":1715083122},"value":11197}, + {"txid":"28154e2008912d27978225c096c22ffe2ea65e1d55bf440ee41c21f9489c7fe1","vout":0,"status":{"confirmed":true,"block_height":2812826,"block_hash":"000000000000001a01d4058773384f2c23aed5a7e5ede252f99e290fa58324a3","block_time":1715083122},"value":11198} +] \ No newline at end of file diff --git a/chains/evm/calls/events/events.go b/chains/evm/calls/events/events.go index 0f190e3a..41cb657c 100644 --- a/chains/evm/calls/events/events.go +++ b/chains/evm/calls/events/events.go @@ -17,6 +17,7 @@ func (es EventSig) GetTopic() common.Hash { const ( DepositSig EventSig = "Deposit(uint8,bytes32,uint64,address,bytes,bytes)" StartKeygenSig EventSig = "StartKeygen()" + StartFrostKeygenSig EventSig = "StartedFROSTKeygen()" KeyRefreshSig EventSig = "KeyRefresh(string)" ProposalExecutionSig EventSig = "ProposalExecution(uint8,uint64,bytes32,bytes)" FeeChangedSig EventSig = "FeeChanged(uint256)" diff --git a/chains/evm/calls/events/listener.go b/chains/evm/calls/events/listener.go index 2ff0d372..36753b19 100644 --- a/chains/evm/calls/events/listener.go +++ b/chains/evm/calls/events/listener.go @@ -137,6 +137,15 @@ func (l *Listener) FetchKeygenEvents(ctx context.Context, contractAddress common return logs, nil } +func (l *Listener) FetchFrostKeygenEvents(ctx context.Context, contractAddress common.Address, startBlock *big.Int, endBlock *big.Int) ([]ethTypes.Log, error) { + logs, err := l.client.FetchEventLogs(ctx, contractAddress, string(StartFrostKeygenSig), startBlock, endBlock) + if err != nil { + return nil, err + } + + return logs, nil +} + func (l *Listener) FetchRefreshEvents(ctx context.Context, contractAddress common.Address, startBlock *big.Int, endBlock *big.Int) ([]*Refresh, error) { logs, err := l.client.FetchEventLogs(ctx, contractAddress, string(KeyRefreshSig), startBlock, endBlock) if err != nil { diff --git a/chains/evm/config.go b/chains/evm/config.go index af91ed45..25c5e968 100644 --- a/chains/evm/config.go +++ b/chains/evm/config.go @@ -24,6 +24,7 @@ type HandlerConfig struct { type EVMConfig struct { GeneralChainConfig chain.GeneralChainConfig Bridge string + FrostKeygen string Handlers []HandlerConfig MaxGasPrice *big.Int GasMultiplier *big.Float @@ -61,6 +62,7 @@ func (c *EVMConfig) String() string { type RawEVMConfig struct { chain.GeneralChainConfig `mapstructure:",squash"` Bridge string `mapstructure:"bridge"` + FrostKeygen string `mapstructure:"frostKeygen"` Handlers []HandlerConfig `mapstrcture:"handlers"` MaxGasPrice int64 `mapstructure:"maxGasPrice" default:"500000000000"` GasMultiplier float64 `mapstructure:"gasMultiplier" default:"1"` @@ -109,6 +111,7 @@ func NewEVMConfig(chainConfig map[string]interface{}) (*EVMConfig, error) { GeneralChainConfig: c.GeneralChainConfig, Handlers: c.Handlers, Bridge: c.Bridge, + FrostKeygen: c.FrostKeygen, BlockRetryInterval: time.Duration(c.BlockRetryInterval) * time.Second, GasLimit: big.NewInt(c.GasLimit), MaxGasPrice: big.NewInt(c.MaxGasPrice), diff --git a/chains/evm/config_test.go b/chains/evm/config_test.go index a102dd2d..bbc55012 100644 --- a/chains/evm/config_test.go +++ b/chains/evm/config_test.go @@ -62,11 +62,12 @@ func (s *NewEVMConfigTestSuite) Test_InvalidBlockConfirmation() { func (s *NewEVMConfigTestSuite) Test_ValidConfig() { rawConfig := map[string]interface{}{ - "id": 1, - "endpoint": "ws://domain.com", - "name": "evm1", - "from": "address", - "bridge": "bridgeAddress", + "id": 1, + "endpoint": "ws://domain.com", + "name": "evm1", + "from": "address", + "bridge": "bridgeAddress", + "frostKeygen": "frostKeygen", } actualConfig, err := evm.NewEVMConfig(rawConfig) @@ -81,6 +82,7 @@ func (s *NewEVMConfigTestSuite) Test_ValidConfig() { Id: id, }, Bridge: "bridgeAddress", + FrostKeygen: "frostKeygen", GasLimit: big.NewInt(15000000), MaxGasPrice: big.NewInt(500000000000), GasMultiplier: big.NewFloat(1), @@ -94,11 +96,12 @@ func (s *NewEVMConfigTestSuite) Test_ValidConfig() { func (s *NewEVMConfigTestSuite) Test_ValidConfigWithCustomTxParams() { rawConfig := map[string]interface{}{ - "id": 1, - "endpoint": "ws://domain.com", - "name": "evm1", - "from": "address", - "bridge": "bridgeAddress", + "id": 1, + "endpoint": "ws://domain.com", + "name": "evm1", + "from": "address", + "bridge": "bridgeAddress", + "frostKeygen": "frostKeygen", "handlers": []evm.HandlerConfig{ { Type: "erc20", @@ -130,7 +133,8 @@ func (s *NewEVMConfigTestSuite) Test_ValidConfigWithCustomTxParams() { Endpoint: "ws://domain.com", Id: id, }, - Bridge: "bridgeAddress", + Bridge: "bridgeAddress", + FrostKeygen: "frostKeygen", Handlers: []evm.HandlerConfig{ { Type: "erc20", diff --git a/chains/evm/executor/executor.go b/chains/evm/executor/executor.go index c2482ba1..348ada6a 100644 --- a/chains/evm/executor/executor.go +++ b/chains/evm/executor/executor.go @@ -20,7 +20,7 @@ import ( "github.com/ChainSafe/sygma-relayer/comm" "github.com/ChainSafe/sygma-relayer/relayer/transfer" "github.com/ChainSafe/sygma-relayer/tss" - "github.com/ChainSafe/sygma-relayer/tss/signing" + "github.com/ChainSafe/sygma-relayer/tss/ecdsa/signing" "github.com/sygmaprotocol/sygma-core/chains/evm/transactor" "github.com/sygmaprotocol/sygma-core/relayer/proposal" ) diff --git a/chains/evm/listener/depositHandlers/erc1155.go b/chains/evm/listener/depositHandlers/erc1155.go index 12c20f0d..a5cc6cb0 100644 --- a/chains/evm/listener/depositHandlers/erc1155.go +++ b/chains/evm/listener/depositHandlers/erc1155.go @@ -1,3 +1,6 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + package depositHandlers import ( diff --git a/chains/evm/listener/depositHandlers/erc1155_test.go b/chains/evm/listener/depositHandlers/erc1155_test.go index c33033be..7a888869 100644 --- a/chains/evm/listener/depositHandlers/erc1155_test.go +++ b/chains/evm/listener/depositHandlers/erc1155_test.go @@ -1,3 +1,6 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + package depositHandlers_test import ( diff --git a/chains/evm/listener/depositHandlers/erc20.go b/chains/evm/listener/depositHandlers/erc20.go index 9b29d36f..c7932819 100644 --- a/chains/evm/listener/depositHandlers/erc20.go +++ b/chains/evm/listener/depositHandlers/erc20.go @@ -1,3 +1,6 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + package depositHandlers import ( diff --git a/chains/evm/listener/depositHandlers/erc20_test.go b/chains/evm/listener/depositHandlers/erc20_test.go index cfa5f71c..362e9c55 100644 --- a/chains/evm/listener/depositHandlers/erc20_test.go +++ b/chains/evm/listener/depositHandlers/erc20_test.go @@ -1,3 +1,6 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + package depositHandlers_test import ( diff --git a/chains/evm/listener/depositHandlers/erc721.go b/chains/evm/listener/depositHandlers/erc721.go index 37d8a610..f6e30d18 100644 --- a/chains/evm/listener/depositHandlers/erc721.go +++ b/chains/evm/listener/depositHandlers/erc721.go @@ -1,3 +1,6 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + package depositHandlers import ( diff --git a/chains/evm/listener/depositHandlers/erc721_test.go b/chains/evm/listener/depositHandlers/erc721_test.go index 48ace4e4..5da0f5a9 100644 --- a/chains/evm/listener/depositHandlers/erc721_test.go +++ b/chains/evm/listener/depositHandlers/erc721_test.go @@ -1,3 +1,6 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + package depositHandlers_test import ( diff --git a/chains/evm/listener/depositHandlers/generic.go b/chains/evm/listener/depositHandlers/generic.go index 0c07f01f..631bb323 100644 --- a/chains/evm/listener/depositHandlers/generic.go +++ b/chains/evm/listener/depositHandlers/generic.go @@ -1,3 +1,6 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + package depositHandlers import ( diff --git a/chains/evm/listener/depositHandlers/generic_test.go b/chains/evm/listener/depositHandlers/generic_test.go index c7cb7707..90d08ce7 100644 --- a/chains/evm/listener/depositHandlers/generic_test.go +++ b/chains/evm/listener/depositHandlers/generic_test.go @@ -1,3 +1,6 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + package depositHandlers_test import ( diff --git a/chains/evm/listener/depositHandlers/permissionless.go b/chains/evm/listener/depositHandlers/permissionless.go index 89a4feb2..2aee7a2f 100644 --- a/chains/evm/listener/depositHandlers/permissionless.go +++ b/chains/evm/listener/depositHandlers/permissionless.go @@ -1,3 +1,6 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + package depositHandlers import ( diff --git a/chains/evm/listener/eventHandlers/event-handler.go b/chains/evm/listener/eventHandlers/event-handler.go index 35da22d5..6577edbf 100644 --- a/chains/evm/listener/eventHandlers/event-handler.go +++ b/chains/evm/listener/eventHandlers/event-handler.go @@ -21,8 +21,9 @@ import ( "github.com/ChainSafe/sygma-relayer/comm/p2p" "github.com/ChainSafe/sygma-relayer/topology" "github.com/ChainSafe/sygma-relayer/tss" - "github.com/ChainSafe/sygma-relayer/tss/keygen" - "github.com/ChainSafe/sygma-relayer/tss/resharing" + "github.com/ChainSafe/sygma-relayer/tss/ecdsa/keygen" + "github.com/ChainSafe/sygma-relayer/tss/ecdsa/resharing" + frostKeygen "github.com/ChainSafe/sygma-relayer/tss/frost/keygen" "github.com/ethereum/go-ethereum/common" ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/libp2p/go-libp2p/core/host" @@ -30,6 +31,7 @@ import ( type EventListener interface { FetchKeygenEvents(ctx context.Context, address common.Address, startBlock *big.Int, endBlock *big.Int) ([]ethTypes.Log, error) + FetchFrostKeygenEvents(ctx context.Context, address common.Address, startBlock *big.Int, endBlock *big.Int) ([]ethTypes.Log, error) FetchRefreshEvents(ctx context.Context, address common.Address, startBlock *big.Int, endBlock *big.Int) ([]*events.Refresh, error) FetchRetryEvents(ctx context.Context, contractAddress common.Address, startBlock *big.Int, endBlock *big.Int) ([]events.RetryEvent, error) FetchRetryDepositEvents(event events.RetryEvent, bridgeAddress common.Address, blockConfirmations *big.Int) ([]events.Deposit, error) @@ -125,7 +127,7 @@ type KeygenEventHandler struct { coordinator *tss.Coordinator host host.Host communication comm.Communication - storer keygen.SaveDataStorer + storer keygen.ECDSAKeyshareStorer bridgeAddress common.Address threshold int } @@ -136,7 +138,7 @@ func NewKeygenEventHandler( coordinator *tss.Coordinator, host host.Host, communication comm.Communication, - storer keygen.SaveDataStorer, + storer keygen.ECDSAKeyshareStorer, bridgeAddress common.Address, threshold int, ) *KeygenEventHandler { @@ -167,7 +169,6 @@ func (eh *KeygenEventHandler) HandleEvents( if err != nil { return fmt.Errorf("unable to fetch keygen events because of: %+v", err) } - if len(keygenEvents) == 0 { return nil } @@ -189,6 +190,76 @@ func (eh *KeygenEventHandler) sessionID(block *big.Int) string { return fmt.Sprintf("keygen-%s", block.String()) } +type FrostKeygenEventHandler struct { + log zerolog.Logger + eventListener EventListener + coordinator *tss.Coordinator + host host.Host + communication comm.Communication + storer frostKeygen.FrostKeyshareStorer + contractAddress common.Address + threshold int +} + +func NewFrostKeygenEventHandler( + logC zerolog.Context, + eventListener EventListener, + coordinator *tss.Coordinator, + host host.Host, + communication comm.Communication, + storer frostKeygen.FrostKeyshareStorer, + contractAddress common.Address, + threshold int, +) *FrostKeygenEventHandler { + return &FrostKeygenEventHandler{ + log: logC.Logger(), + eventListener: eventListener, + coordinator: coordinator, + host: host, + communication: communication, + storer: storer, + contractAddress: contractAddress, + threshold: threshold, + } +} + +func (eh *FrostKeygenEventHandler) HandleEvents( + startBlock *big.Int, + endBlock *big.Int, +) error { + key, err := eh.storer.GetKeyshare() + if (key.Threshold != 0) && (err == nil) { + return nil + } + + keygenEvents, err := eh.eventListener.FetchFrostKeygenEvents( + context.Background(), eh.contractAddress, startBlock, endBlock, + ) + if err != nil { + return fmt.Errorf("unable to fetch keygen events because of: %+v", err) + } + + if len(keygenEvents) == 0 { + return nil + } + + eh.log.Info().Msgf( + "Resolved FROST keygen message in block range: %s-%s", startBlock.String(), endBlock.String(), + ) + + keygenBlockNumber := big.NewInt(0).SetUint64(keygenEvents[0].BlockNumber) + keygen := frostKeygen.NewKeygen(eh.sessionID(keygenBlockNumber), eh.threshold, eh.host, eh.communication, eh.storer) + err = eh.coordinator.Execute(context.Background(), keygen, make(chan interface{}, 1)) + if err != nil { + log.Err(err).Msgf("Failed executing keygen") + } + return nil +} + +func (eh *FrostKeygenEventHandler) sessionID(block *big.Int) string { + return fmt.Sprintf("frost-keygen-%s", block.String()) +} + type RefreshEventHandler struct { log zerolog.Logger topologyProvider topology.NetworkTopologyProvider diff --git a/chains/evm/listener/eventHandlers/mock/listener.go b/chains/evm/listener/eventHandlers/mock/listener.go index e8cc6558..954575ad 100644 --- a/chains/evm/listener/eventHandlers/mock/listener.go +++ b/chains/evm/listener/eventHandlers/mock/listener.go @@ -54,6 +54,21 @@ func (mr *MockEventListenerMockRecorder) FetchDeposits(ctx, address, startBlock, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchDeposits", reflect.TypeOf((*MockEventListener)(nil).FetchDeposits), ctx, address, startBlock, endBlock) } +// FetchFrostKeygenEvents mocks base method. +func (m *MockEventListener) FetchFrostKeygenEvents(ctx context.Context, address common.Address, startBlock, endBlock *big.Int) ([]types.Log, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FetchFrostKeygenEvents", ctx, address, startBlock, endBlock) + ret0, _ := ret[0].([]types.Log) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FetchFrostKeygenEvents indicates an expected call of FetchFrostKeygenEvents. +func (mr *MockEventListenerMockRecorder) FetchFrostKeygenEvents(ctx, address, startBlock, endBlock interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchFrostKeygenEvents", reflect.TypeOf((*MockEventListener)(nil).FetchFrostKeygenEvents), ctx, address, startBlock, endBlock) +} + // FetchKeygenEvents mocks base method. func (m *MockEventListener) FetchKeygenEvents(ctx context.Context, address common.Address, startBlock, endBlock *big.Int) ([]types.Log, error) { m.ctrl.T.Helper() diff --git a/chains/substrate/executor/executor.go b/chains/substrate/executor/executor.go index 15452dc0..d3f8252f 100644 --- a/chains/substrate/executor/executor.go +++ b/chains/substrate/executor/executor.go @@ -25,7 +25,7 @@ import ( "github.com/ChainSafe/sygma-relayer/comm" "github.com/ChainSafe/sygma-relayer/tss" - "github.com/ChainSafe/sygma-relayer/tss/signing" + "github.com/ChainSafe/sygma-relayer/tss/ecdsa/signing" ) var ( diff --git a/chains/substrate/listener/util.go b/chains/substrate/listener/util.go index 6711c19c..fef650fb 100644 --- a/chains/substrate/listener/util.go +++ b/chains/substrate/listener/util.go @@ -1,3 +1,6 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + package listener import ( diff --git a/chains/substrate/listener/util_test.go b/chains/substrate/listener/util_test.go index 21443010..35eb06a8 100644 --- a/chains/substrate/listener/util_test.go +++ b/chains/substrate/listener/util_test.go @@ -1,3 +1,6 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + package listener_test import ( diff --git a/comm/elector/bully.go b/comm/elector/bully.go index bf833e49..b7836b8a 100644 --- a/comm/elector/bully.go +++ b/comm/elector/bully.go @@ -10,7 +10,7 @@ import ( "github.com/ChainSafe/sygma-relayer/comm" "github.com/ChainSafe/sygma-relayer/config/relayer" - "github.com/ChainSafe/sygma-relayer/tss/common" + "github.com/ChainSafe/sygma-relayer/tss/util" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" "github.com/rs/zerolog/log" @@ -28,7 +28,7 @@ type bullyCoordinatorElector struct { conf relayer.BullyConfig mu *sync.RWMutex coordinator peer.ID - sortedPeers common.SortablePeerSlice + sortedPeers util.SortablePeerSlice } func NewBullyCoordinatorElector( @@ -59,7 +59,7 @@ func (bc *bullyCoordinatorElector) Coordinator(ctx context.Context, peers peer.I go bc.listen(ctx) defer cancel() - bc.sortedPeers = common.SortPeersForSession(peers, bc.sessionID) + bc.sortedPeers = util.SortPeersForSession(peers, bc.sessionID) errChan := make(chan error) go bc.startBullyCoordination(errChan) diff --git a/comm/elector/bully_test.go b/comm/elector/bully_test.go index 118f6a21..16774695 100644 --- a/comm/elector/bully_test.go +++ b/comm/elector/bully_test.go @@ -10,11 +10,11 @@ import ( "time" "github.com/ChainSafe/sygma-relayer/comm/elector" + "github.com/ChainSafe/sygma-relayer/tss/util" "github.com/ChainSafe/sygma-relayer/comm/p2p" "github.com/ChainSafe/sygma-relayer/config/relayer" "github.com/ChainSafe/sygma-relayer/topology" - "github.com/ChainSafe/sygma-relayer/tss/common" "github.com/golang/mock/gomock" "github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/host" @@ -85,7 +85,7 @@ func (s *BullyTestSuite) SetupIndividualTest(c BullyTestCase) ([]elector.Coordin allowedPeers = append(allowedPeers, newHost.ID()) } - sortedPeers := common.SortPeersForSession(allowedPeers, s.testSessionID) + sortedPeers := util.SortPeersForSession(allowedPeers, s.testSessionID) initialCoordinator := sortedPeers[0].ID var finalCoordinator peer.ID if !c.isLeaderActive { diff --git a/comm/elector/static.go b/comm/elector/static.go index 4928e125..1ac8e24b 100644 --- a/comm/elector/static.go +++ b/comm/elector/static.go @@ -6,7 +6,7 @@ package elector import ( "context" - "github.com/ChainSafe/sygma-relayer/tss/common" + "github.com/ChainSafe/sygma-relayer/tss/util" "github.com/libp2p/go-libp2p/core/peer" ) @@ -19,9 +19,9 @@ func NewCoordinatorElector(sessionID string) CoordinatorElector { } func (s *staticCoordinatorElector) Coordinator(ctx context.Context, peers peer.IDSlice) (peer.ID, error) { - sortedPeers := common.SortPeersForSession(peers, s.sessionID) + sortedPeers := util.SortPeersForSession(peers, s.sessionID) if len(sortedPeers) == 0 { return peer.ID(""), nil } - return common.SortPeersForSession(peers, s.sessionID)[0].ID, nil + return util.SortPeersForSession(peers, s.sessionID)[0].ID, nil } diff --git a/comm/p2p/libp2p_test.go b/comm/p2p/libp2p_test.go index c7560482..c58e9484 100644 --- a/comm/p2p/libp2p_test.go +++ b/comm/p2p/libp2p_test.go @@ -13,7 +13,7 @@ import ( mock_host "github.com/ChainSafe/sygma-relayer/comm/p2p/mock/host" mock_network "github.com/ChainSafe/sygma-relayer/comm/p2p/mock/stream" "github.com/ChainSafe/sygma-relayer/topology" - "github.com/ChainSafe/sygma-relayer/tss/common" + "github.com/ChainSafe/sygma-relayer/tss/message" "github.com/golang/mock/gomock" "github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/host" @@ -176,7 +176,7 @@ func (s *Libp2pCommunicationTestSuite) TestLibp2pCommunication_SendReceiveMessag s.Nil(err) pingMsg := <-msgChn - msgBytes, _ := common.MarshalTssMessage([]byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), true) + msgBytes, _ := message.MarshalTssMessage([]byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), true) err = communications[0].Broadcast([]peer.ID{testHosts[1].ID()}, msgBytes, comm.TssKeySignMsg, "2") s.Nil(err) largeMsg := <-msgChn diff --git a/config/chain/config.go b/config/chain/config.go index 905cfff5..9330f5e7 100644 --- a/config/chain/config.go +++ b/config/chain/config.go @@ -1,3 +1,6 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + package chain import ( diff --git a/config/chain/config_test.go b/config/chain/config_test.go index b41fce20..efefd644 100644 --- a/config/chain/config_test.go +++ b/config/chain/config_test.go @@ -1,3 +1,6 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + package chain import ( diff --git a/config/config_test.go b/config/config_test.go index b5fdf6c8..32181505 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -64,6 +64,7 @@ func (s *GetConfigTestSuite) Test_GetConfigFromENV() { _ = os.Setenv("SYG_RELAYER_MPCCONFIG_KEY", "test-pk") _ = os.Setenv("SYG_RELAYER_MPCCONFIG_KEYSHAREPATH", "/cfg/keyshares/0.keyshare") + _ = os.Setenv("SYG_RELAYER_MPCCONFIG_FROSTKEYSHAREPATH", "/cfg/keyshares/0-frost.keyshare") _ = os.Setenv("SYG_RELAYER_MPCCONFIG_PORT", "9000") _ = os.Setenv("SYG_RELAYER_MPCCONFIG_TOPOLOGYCONFIGURATION_ENCRYPTIONKEY", "test-enc-key") _ = os.Setenv("SYG_RELAYER_MPCCONFIG_TOPOLOGYCONFIGURATION_URL", "http://test.com") @@ -97,6 +98,7 @@ func (s *GetConfigTestSuite) Test_GetConfigFromENV() { }, Port: 9000, KeysharePath: "/cfg/keyshares/0.keyshare", + FrostKeysharePath: "/cfg/keyshares/0-frost.keyshare", Key: "test-pk", CommHealthCheckInterval: 5 * time.Minute, }, diff --git a/config/relayer/config.go b/config/relayer/config.go index 155ae0f9..982039f8 100644 --- a/config/relayer/config.go +++ b/config/relayer/config.go @@ -27,6 +27,7 @@ type MpcRelayerConfig struct { TopologyConfiguration TopologyConfiguration Port uint16 KeysharePath string + FrostKeysharePath string Key string CommHealthCheckInterval time.Duration } @@ -58,6 +59,7 @@ type RawRelayerConfig struct { type RawMpcRelayerConfig struct { KeysharePath string `mapstructure:"KeysharePath" json:"keysharePath"` + FrostKeysharePath string `mapstructure:"FrostKeysharePath" json:"frostKeysharePath"` Key string `mapstructure:"Key" json:"key"` Port string `mapstructure:"Port" json:"port" default:"9000"` TopologyConfiguration TopologyConfiguration `mapstructure:"TopologyConfiguration" json:"topologyConfiguration"` @@ -135,6 +137,7 @@ func parseMpcConfig(rawConfig RawRelayerConfig) (MpcRelayerConfig, error) { mpcConfig.TopologyConfiguration = rawConfig.MpcConfig.TopologyConfiguration mpcConfig.KeysharePath = rawConfig.MpcConfig.KeysharePath + mpcConfig.FrostKeysharePath = rawConfig.MpcConfig.FrostKeysharePath mpcConfig.Key = rawConfig.MpcConfig.Key duration, err := time.ParseDuration(rawConfig.MpcConfig.CommHealthCheckInterval) diff --git a/e2e/btc/btc_test.go b/e2e/btc/btc_test.go new file mode 100644 index 00000000..c7452bb9 --- /dev/null +++ b/e2e/btc/btc_test.go @@ -0,0 +1,129 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package btc_test + +import ( + "context" + + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/ethereum/go-ethereum/core/types" + evmClient "github.com/sygmaprotocol/sygma-core/chains/evm/client" + "github.com/sygmaprotocol/sygma-core/chains/evm/transactor" + "github.com/sygmaprotocol/sygma-core/chains/evm/transactor/gas" + "github.com/sygmaprotocol/sygma-core/chains/evm/transactor/signAndSend" + "github.com/sygmaprotocol/sygma-core/chains/evm/transactor/transaction" + + "math/big" + "testing" + + "github.com/ChainSafe/sygma-relayer/chains/btc/connection" + "github.com/ChainSafe/sygma-relayer/chains/evm/calls/contracts/bridge" + + "github.com/ChainSafe/sygma-relayer/e2e/btc" + "github.com/ChainSafe/sygma-relayer/e2e/evm" + "github.com/ChainSafe/sygma-relayer/e2e/evm/contracts/erc20" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/suite" +) + +var amountToDeposit = big.NewInt(100000000) + +type TestClient interface { + evm.EVMClient + LatestBlock() (*big.Int, error) + CodeAt(ctx context.Context, contractAddress common.Address, block *big.Int) ([]byte, error) + FetchEventLogs(ctx context.Context, contractAddress common.Address, event string, startBlock *big.Int, endBlock *big.Int) ([]types.Log, error) + SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) + TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) + BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) +} + +func Test_EVMBtc(t *testing.T) { + evmConfig := evm.DEFAULT_CONFIG + evmConfig.Erc20LockReleaseAddr = common.HexToAddress("0xd3Eb00fCE476aEFdC76A02F3531b7A0C6D5238B3") + evmConfig.Erc20LockReleaseResourceID = evm.SliceTo32Bytes(common.LeftPadBytes([]byte{0x10}, 31)) + + ethClient, err := evmClient.NewEVMClient(evm.ETHEndpoint1, evm.AdminAccount) + if err != nil { + panic(err) + } + gasPricer := gas.NewStaticGasPriceDeterminant(ethClient, nil) + + suite.Run( + t, + NewEVMBtcTestSuite( + transaction.NewTransaction, + ethClient, + gasPricer, + evmConfig, + ), + ) +} + +func NewEVMBtcTestSuite( + fabric transaction.TxFabric, + evmClient TestClient, + gasPricer signAndSend.GasPricer, + evmConfig evm.BridgeConfig, +) *IntegrationTestSuite { + return &IntegrationTestSuite{ + fabric: fabric, + evmClient: evmClient, + gasPricer: gasPricer, + evmConfig: evmConfig, + } +} + +type IntegrationTestSuite struct { + suite.Suite + fabric transaction.TxFabric + evmClient TestClient + gasPricer signAndSend.GasPricer + evmConfig evm.BridgeConfig +} + +func (s *IntegrationTestSuite) SetupSuite() { + // EVM side preparation + evmTransactor := signAndSend.NewSignAndSendTransactor(s.fabric, s.gasPricer, s.evmClient) + mintTo := s.evmClient.From() + amountToMint := big.NewInt(0).Mul(big.NewInt(5000000000000000), big.NewInt(0).Exp(big.NewInt(10), big.NewInt(18), nil)) + amountToApprove := big.NewInt(0).Mul(big.NewInt(100000), big.NewInt(0).Exp(big.NewInt(10), big.NewInt(18), nil)) + erc20LRContract := erc20.NewERC20Contract(s.evmClient, s.evmConfig.Erc20LockReleaseAddr, evmTransactor) + _, err := erc20LRContract.MintTokens(mintTo, amountToMint, transactor.TransactOptions{}) + if err != nil { + panic(err) + } + _, err = erc20LRContract.ApproveTokens(s.evmConfig.Erc20LockReleaseHandlerAddr, amountToApprove, transactor.TransactOptions{}) + if err != nil { + panic(err) + } +} + +func (s *IntegrationTestSuite) Test_Erc20Deposit_EVM_to_Btc() { + conn, err := connection.NewBtcConnection(btc.BtcEndpoint, "user", "password", true) + s.Nil(err) + + addr, err := btcutil.DecodeAddress("bcrt1pja8aknn7te4empmghnyqnrtjqn0lyg5zy3p5jsdp4le930wnpnxsrtd3ht", &chaincfg.RegressionNetParams) + s.Nil(err) + balanceBefore, err := conn.ListUnspentMinMaxAddresses(0, 1000, []btcutil.Address{addr}) + s.Nil(err) + s.Equal(len(balanceBefore), 0) + + transactor1 := signAndSend.NewSignAndSendTransactor(s.fabric, s.gasPricer, s.evmClient) + bridgeContract1 := bridge.NewBridgeContract(s.evmClient, s.evmConfig.BridgeAddr, transactor1) + erc20DepositData := evm.ConstructErc20DepositData([]byte("bcrt1pja8aknn7te4empmghnyqnrtjqn0lyg5zy3p5jsdp4le930wnpnxsrtd3ht"), amountToDeposit) + _, err = bridgeContract1.ExecuteTransaction("deposit", transactor.TransactOptions{Value: s.evmConfig.BasicFee}, uint8(4), s.evmConfig.Erc20LockReleaseResourceID, erc20DepositData, []byte{}) + s.Nil(err) + + err = btc.WaitForProposalExecuted(conn, addr) + s.Nil(err) + + balanceAfter, err := conn.ListUnspentMinMaxAddresses(0, 1000, []btcutil.Address{addr}) + s.Nil(err) + s.Equal(balanceAfter[0].Amount*100000000, float64(amountToDeposit.Int64())) + +} diff --git a/e2e/btc/util.go b/e2e/btc/util.go new file mode 100644 index 00000000..4d185ae6 --- /dev/null +++ b/e2e/btc/util.go @@ -0,0 +1,38 @@ +package btc + +import ( + "errors" + "time" + + "github.com/ChainSafe/sygma-relayer/chains/btc/connection" + "github.com/btcsuite/btcd/btcutil" +) + +const ( + BtcEndpoint = "localhost:18443" +) + +var TestTimeout = time.Minute * 3 + +func WaitForProposalExecuted(conn *connection.Connection, addr btcutil.Address) error { + timeout := time.After(TestTimeout) + ticker := time.NewTicker(5 * time.Second) + + for { + select { + case <-ticker.C: + balance, err := conn.ListUnspentMinMaxAddresses(0, 1000, []btcutil.Address{addr}) + if err != nil { + ticker.Stop() + return err + } + if len(balance) > 0 { + ticker.Stop() + return nil + } + case <-timeout: + ticker.Stop() + return errors.New("test timed out waiting for proposal execution event") + } + } +} diff --git a/e2e/evm/contracts/centrifuge/assetStore.go b/e2e/evm/contracts/centrifuge/assetStore.go index 603b4b4e..6e896b36 100644 --- a/e2e/evm/contracts/centrifuge/assetStore.go +++ b/e2e/evm/contracts/centrifuge/assetStore.go @@ -1,3 +1,6 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + package centrifuge import ( diff --git a/e2e/evm/contracts/centrifuge/consts.go b/e2e/evm/contracts/centrifuge/consts.go index b2e5bad4..cdfbb578 100644 --- a/e2e/evm/contracts/centrifuge/consts.go +++ b/e2e/evm/contracts/centrifuge/consts.go @@ -1,3 +1,6 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + package centrifuge const CentrifugeAssetStoreABI = `[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"asset","type":"bytes32"}],"name":"AssetStored","type":"event"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"_assetsStored","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"asset","type":"bytes32"}],"name":"store","outputs":[],"stateMutability":"nonpayable","type":"function"}]` diff --git a/e2e/evm/contracts/erc1155/consts.go b/e2e/evm/contracts/erc1155/consts.go index d9c2ec41..6dd13018 100644 --- a/e2e/evm/contracts/erc1155/consts.go +++ b/e2e/evm/contracts/erc1155/consts.go @@ -1,3 +1,6 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + package erc1155 const ERC1155PresetMinterPauserABI = `[{"inputs":[{"internalType":"string","name":"uri","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"values","type":"uint256[]"}],"name":"TransferBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"TransferSingle","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"value","type":"string"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"URI","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"accounts","type":"address[]"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"}],"name":"balanceOfBatch","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"uint256[]","name":"values","type":"uint256[]"}],"name":"burnBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"mintBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeBatchTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"uri","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}]` diff --git a/e2e/evm/contracts/erc1155/erc1155.go b/e2e/evm/contracts/erc1155/erc1155.go index e65d24a6..9948df54 100644 --- a/e2e/evm/contracts/erc1155/erc1155.go +++ b/e2e/evm/contracts/erc1155/erc1155.go @@ -1,3 +1,6 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + package erc1155 import ( diff --git a/e2e/evm/contracts/erc20/consts.go b/e2e/evm/contracts/erc20/consts.go index 6a10a67c..bd9e95d6 100644 --- a/e2e/evm/contracts/erc20/consts.go +++ b/e2e/evm/contracts/erc20/consts.go @@ -1,3 +1,6 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + package erc20 const ERC20PresetMinterPauserABI = `[{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burnFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"}]` diff --git a/e2e/evm/contracts/erc20/erc20.go b/e2e/evm/contracts/erc20/erc20.go index 00dc2d8f..d6842abc 100644 --- a/e2e/evm/contracts/erc20/erc20.go +++ b/e2e/evm/contracts/erc20/erc20.go @@ -1,3 +1,6 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + package erc20 import ( diff --git a/e2e/evm/contracts/erc721/consts.go b/e2e/evm/contracts/erc721/consts.go index d348b328..c7d7362f 100644 --- a/e2e/evm/contracts/erc721/consts.go +++ b/e2e/evm/contracts/erc721/consts.go @@ -1,3 +1,6 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + package erc721 const ERC721PresetMinterPauserABI = `[{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"string","name":"baseURI","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"getRoleMemberIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"string","name":"_data","type":"string"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}]` diff --git a/e2e/evm/contracts/erc721/erc721.go b/e2e/evm/contracts/erc721/erc721.go index be380efc..7fcd0982 100644 --- a/e2e/evm/contracts/erc721/erc721.go +++ b/e2e/evm/contracts/erc721/erc721.go @@ -1,3 +1,6 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + package erc721 import ( diff --git a/e2e/evm/evm_test.go b/e2e/evm/evm_test.go index f6a45aef..f1f63d15 100644 --- a/e2e/evm/evm_test.go +++ b/e2e/evm/evm_test.go @@ -31,7 +31,6 @@ import ( "github.com/sygmaprotocol/sygma-core/chains/evm/transactor/gas" "github.com/sygmaprotocol/sygma-core/chains/evm/transactor/signAndSend" "github.com/sygmaprotocol/sygma-core/chains/evm/transactor/transaction" - "github.com/sygmaprotocol/sygma-core/crypto/secp256k1" "github.com/ChainSafe/sygma-relayer/chains/evm/calls/contracts/bridge" "github.com/ChainSafe/sygma-relayer/e2e/evm" @@ -47,51 +46,16 @@ type TestClient interface { BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) } -const ETHEndpoint1 = "ws://localhost:8545" -const ETHEndpoint2 = "ws://localhost:8547" - // Alice key is used by the relayer, Charlie key is used as admin and depositer func Test_EVM2EVM(t *testing.T) { rand.Seed(time.Now().Unix()) - config := evm.BridgeConfig{ - BridgeAddr: common.HexToAddress("0x6CdE2Cd82a4F8B74693Ff5e194c19CA08c2d1c68"), - - Erc20Addr: common.HexToAddress("0x78E5b9cEC9aEA29071f070C8cC561F692B3511A6"), - Erc20HandlerAddr: common.HexToAddress("0x02091EefF969b33A5CE8A729DaE325879bf76f90"), - Erc20ResourceID: evm.SliceTo32Bytes(common.LeftPadBytes([]byte{0}, 31)), - - Erc20LockReleaseAddr: common.HexToAddress("0x1ED1d77911944622FCcDDEad8A731fd77E94173e"), - Erc20LockReleaseHandlerAddr: common.HexToAddress("0x02091EefF969b33A5CE8A729DaE325879bf76f90"), - Erc20LockReleaseResourceID: evm.SliceTo32Bytes(common.LeftPadBytes([]byte{3}, 31)), - - Erc721Addr: common.HexToAddress("0xd3Eb00fCE476aEFdC76A02F3531b7A0C6D5238B3"), - Erc721HandlerAddr: common.HexToAddress("0xC2D334e2f27A9dB2Ed8C4561De86C1A00EBf6760"), - Erc721ResourceID: evm.SliceTo32Bytes(common.LeftPadBytes([]byte{2}, 31)), - - GenericHandlerAddr: common.HexToAddress("0xa4640d1315Be1f88aC4F81546AA2C785cf247C31"), - GenericResourceID: evm.SliceTo32Bytes(common.LeftPadBytes([]byte{1}, 31)), - AssetStoreAddr: common.HexToAddress("0x979C2e7347c9831E18870aB886f0101EBC771CeB"), - - PermissionlessGenericHandlerAddr: common.HexToAddress("0xa2451c8553371E754F5e93A440aDcCa1c0DcF395"), - PermissionlessGenericResourceID: evm.SliceTo32Bytes(common.LeftPadBytes([]byte{5}, 31)), - - Erc1155Addr: common.HexToAddress("0x5e6924e6A120bd833617D0873f0a1b747ee2D743"), - Erc1155HandlerAddr: common.HexToAddress("0x156fA85e1df5d69B0F138dcEbAa5a14ca640FaED"), - Erc1155ResourceID: evm.SliceTo32Bytes(common.LeftPadBytes([]byte{4}, 31)), - - BasicFeeHandlerAddr: common.HexToAddress("0x1CcB4231f2ff299E1E049De76F0a1D2B415C563A"), - FeeRouterAddress: common.HexToAddress("0xF28c11CB14C6d2B806f99EA8b138F65e74a1Ed66"), - BasicFee: evm.BasicFee, - } - - pk, _ := secp256k1.NewKeypairFromString("cc2c32b154490f09f70c1c8d4b997238448d649e0777495863db231c4ced3616") - ethClient1, err := client.NewEVMClient(ETHEndpoint1, pk) + ethClient1, err := client.NewEVMClient(evm.ETHEndpoint1, evm.AdminAccount) if err != nil { panic(err) } gasPricer1 := gas.NewStaticGasPriceDeterminant(ethClient1, nil) - ethClient2, err := client.NewEVMClient(ETHEndpoint2, pk) + ethClient2, err := client.NewEVMClient(evm.ETHEndpoint2, evm.AdminAccount) if err != nil { panic(err) } @@ -106,8 +70,8 @@ func Test_EVM2EVM(t *testing.T) { ethClient2, gasPricer1, gasPricer2, - config, - config, + evm.DEFAULT_CONFIG, + evm.DEFAULT_CONFIG, ), ) } diff --git a/e2e/evm/util.go b/e2e/evm/util.go index 16e44f02..4dc76225 100644 --- a/e2e/evm/util.go +++ b/e2e/evm/util.go @@ -11,6 +11,7 @@ import ( "github.com/sygmaprotocol/sygma-core/chains/evm/client" "github.com/sygmaprotocol/sygma-core/chains/evm/transactor/gas" + "github.com/sygmaprotocol/sygma-core/crypto/secp256k1" "github.com/ChainSafe/sygma-relayer/chains/evm/calls/events" "github.com/ChainSafe/sygma-relayer/chains/evm/listener/depositHandlers" @@ -21,9 +22,18 @@ import ( ) var TestTimeout = time.Minute * 4 -var BasicFee = big.NewInt(100000000000000) -var OracleFee = uint16(500) // 5% - multiplied by 100 to not lose precision on contract side -var GasUsed = uint32(2000000000) + +var ( + BasicFee = big.NewInt(100000000000000) + AdminAccount, _ = secp256k1.NewKeypairFromString("cc2c32b154490f09f70c1c8d4b997238448d649e0777495863db231c4ced3616") +) + +const ( + ETHEndpoint1 = "ws://localhost:8545" + ETHEndpoint2 = "ws://localhost:8547" + OracleFee = uint16(500) // 5% - multiplied by 100 to not lose precision on contract side + GasUsed = uint32(2000000000) +) type Client interface { LatestBlock() (*big.Int, error) @@ -71,6 +81,37 @@ type BridgeConfig struct { GasMultiplier *big.Float } +var DEFAULT_CONFIG = BridgeConfig{ + BridgeAddr: common.HexToAddress("0x6CdE2Cd82a4F8B74693Ff5e194c19CA08c2d1c68"), + + Erc20Addr: common.HexToAddress("0x78E5b9cEC9aEA29071f070C8cC561F692B3511A6"), + Erc20HandlerAddr: common.HexToAddress("0x02091EefF969b33A5CE8A729DaE325879bf76f90"), + Erc20ResourceID: SliceTo32Bytes(common.LeftPadBytes([]byte{0}, 31)), + + Erc20LockReleaseAddr: common.HexToAddress("0x1ED1d77911944622FCcDDEad8A731fd77E94173e"), + Erc20LockReleaseHandlerAddr: common.HexToAddress("0x02091EefF969b33A5CE8A729DaE325879bf76f90"), + Erc20LockReleaseResourceID: SliceTo32Bytes(common.LeftPadBytes([]byte{3}, 31)), + + Erc721Addr: common.HexToAddress("0xa4640d1315Be1f88aC4F81546AA2C785cf247C31"), + Erc721HandlerAddr: common.HexToAddress("0xC2D334e2f27A9dB2Ed8C4561De86C1A00EBf6760"), + Erc721ResourceID: SliceTo32Bytes(common.LeftPadBytes([]byte{2}, 31)), + + GenericHandlerAddr: common.HexToAddress("0xF956Ba663bd563f585e00D5973E06b443E5C4D65"), + GenericResourceID: SliceTo32Bytes(common.LeftPadBytes([]byte{1}, 31)), + AssetStoreAddr: common.HexToAddress("0xa2451c8553371E754F5e93A440aDcCa1c0DcF395"), + + PermissionlessGenericHandlerAddr: common.HexToAddress("0x156fA85e1df5d69B0F138dcEbAa5a14ca640FaED"), + PermissionlessGenericResourceID: SliceTo32Bytes(common.LeftPadBytes([]byte{5}, 31)), + + Erc1155Addr: common.HexToAddress("0xFfd243c2C27e303e6d96aA4F7b3840Bf7209F0d7"), + Erc1155HandlerAddr: common.HexToAddress("0x9Fd58882b82EFaD2867f7eaB43539907bc07C360"), + Erc1155ResourceID: SliceTo32Bytes(common.LeftPadBytes([]byte{4}, 31)), + + BasicFeeHandlerAddr: common.HexToAddress("0x1CcB4231f2ff299E1E049De76F0a1D2B415C563A"), + FeeRouterAddress: common.HexToAddress("0xF28c11CB14C6d2B806f99EA8b138F65e74a1Ed66"), + BasicFee: BasicFee, +} + func WaitForProposalExecuted(client Client, bridge common.Address) error { startBlock, _ := client.LatestBlock() diff --git a/e2e/substrate/substrate_test.go b/e2e/substrate/substrate_test.go index 3ff23836..e728ff2f 100644 --- a/e2e/substrate/substrate_test.go +++ b/e2e/substrate/substrate_test.go @@ -16,7 +16,6 @@ import ( "github.com/sygmaprotocol/sygma-core/chains/evm/transactor/transaction" "github.com/sygmaprotocol/sygma-core/chains/substrate/client" "github.com/sygmaprotocol/sygma-core/chains/substrate/connection" - "github.com/sygmaprotocol/sygma-core/crypto/secp256k1" "math/big" "testing" @@ -32,9 +31,6 @@ import ( "github.com/stretchr/testify/suite" ) -const ETHEndpoint = "ws://localhost:8545" -const SubstrateEndpoint = "ws://localhost:9944" - type TestClient interface { evm.EVMClient LatestBlock() (*big.Int, error) @@ -46,31 +42,13 @@ type TestClient interface { } func Test_EVMSubstrate(t *testing.T) { - // EVM side config - evmConfig := evm.BridgeConfig{ - BridgeAddr: common.HexToAddress("0x6CdE2Cd82a4F8B74693Ff5e194c19CA08c2d1c68"), - - Erc20Addr: common.HexToAddress("0x78E5b9cEC9aEA29071f070C8cC561F692B3511A6"), - Erc20HandlerAddr: common.HexToAddress("0x02091EefF969b33A5CE8A729DaE325879bf76f90"), - Erc20ResourceID: evm.SliceTo32Bytes(common.LeftPadBytes([]byte{0}, 31)), - - Erc20LockReleaseAddr: common.HexToAddress("0x1ED1d77911944622FCcDDEad8A731fd77E94173e"), - Erc20LockReleaseHandlerAddr: common.HexToAddress("0x02091EefF969b33A5CE8A729DaE325879bf76f90"), - Erc20LockReleaseResourceID: evm.SliceTo32Bytes(common.LeftPadBytes([]byte{3}, 31)), - - BasicFeeHandlerAddr: common.HexToAddress("0x1CcB4231f2ff299E1E049De76F0a1D2B415C563A"), - FeeRouterAddress: common.HexToAddress("0xF28c11CB14C6d2B806f99EA8b138F65e74a1Ed66"), - BasicFee: evm.BasicFee, - } - - pk, _ := secp256k1.NewKeypairFromString("cc2c32b154490f09f70c1c8d4b997238448d649e0777495863db231c4ced3616") - ethClient, err := evmClient.NewEVMClient(ETHEndpoint, pk) + ethClient, err := evmClient.NewEVMClient(evm.ETHEndpoint1, evm.AdminAccount) if err != nil { panic(err) } gasPricer := gas.NewStaticGasPriceDeterminant(ethClient, nil) - substrateConnection, err := connection.NewSubstrateConnection(SubstrateEndpoint) + substrateConnection, err := connection.NewSubstrateConnection(substrate.SubstrateEndpoint) if err != nil { panic(err) } @@ -88,7 +66,7 @@ func Test_EVMSubstrate(t *testing.T) { substrateClient, substrateConnection, gasPricer, - evmConfig, + evm.DEFAULT_CONFIG, assetIdSerialized, ), ) diff --git a/e2e/substrate/util.go b/e2e/substrate/util.go index 76cee124..c844ca7c 100644 --- a/e2e/substrate/util.go +++ b/e2e/substrate/util.go @@ -33,6 +33,10 @@ var SubstratePK = signature.KeyringPair{ Address: "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", } +const ( + SubstrateEndpoint = "ws://localhost:9944" +) + type USDCAsset struct{} func (a USDCAsset) Encode(encoder scale.Encoder) error { diff --git a/example/Dockerfile b/example/Dockerfile index 1d4d1652..ab8975f9 100644 --- a/example/Dockerfile +++ b/example/Dockerfile @@ -1,6 +1,9 @@ # Copyright 2020 ChainSafe Systems # SPDX-License-Identifier: LGPL-3.0-only +FROM alpine:3.6 as alpine +RUN apk add -U --no-cache ca-certificates + FROM golang:1.19 AS builder ADD . /src WORKDIR /src @@ -12,5 +15,6 @@ RUN go build -ldflags "-X google.golang.org/protobuf/reflect/protoregistry.confl FROM debian:stable-slim COPY --from=builder /bridge ./ RUN chmod +x ./bridge +COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ CMD ["./bridge"] diff --git a/example/app/app.go b/example/app/app.go index e6e43c54..0b00a140 100644 --- a/example/app/app.go +++ b/example/app/app.go @@ -12,9 +12,12 @@ import ( "syscall" "time" + "github.com/ChainSafe/sygma-relayer/chains/btc" + "github.com/ChainSafe/sygma-relayer/chains/btc/mempool" substrateListener "github.com/ChainSafe/sygma-relayer/chains/substrate/listener" substratePallet "github.com/ChainSafe/sygma-relayer/chains/substrate/pallet" "github.com/ChainSafe/sygma-relayer/relayer/transfer" + propStore "github.com/ChainSafe/sygma-relayer/store" "github.com/sygmaprotocol/sygma-core/chains/evm/listener" "github.com/sygmaprotocol/sygma-core/chains/evm/transactor/gas" "github.com/sygmaprotocol/sygma-core/chains/evm/transactor/transaction" @@ -37,6 +40,10 @@ import ( "github.com/sygmaprotocol/sygma-core/chains/evm/transactor/monitored" "github.com/sygmaprotocol/sygma-core/relayer/message" + btcConfig "github.com/ChainSafe/sygma-relayer/chains/btc/config" + btcConnection "github.com/ChainSafe/sygma-relayer/chains/btc/connection" + btcExecutor "github.com/ChainSafe/sygma-relayer/chains/btc/executor" + btcListener "github.com/ChainSafe/sygma-relayer/chains/btc/listener" "github.com/ChainSafe/sygma-relayer/chains/evm" "github.com/ChainSafe/sygma-relayer/chains/substrate" substrateExecutor "github.com/ChainSafe/sygma-relayer/chains/substrate/executor" @@ -98,7 +105,9 @@ func Run() error { communication := p2p.NewCommunication(host, "p2p/sygma") electorFactory := elector.NewCoordinatorElectorFactory(host, configuration.RelayerConfig.BullyConfig) coordinator := tss.NewCoordinator(host, communication, electorFactory) - keyshareStore := keyshare.NewKeyshareStore(configuration.RelayerConfig.MpcConfig.KeysharePath) + keyshareStore := keyshare.NewECDSAKeyshareStore(configuration.RelayerConfig.MpcConfig.KeysharePath) + frostKeyshareStore := keyshare.NewFrostKeyshareStore(configuration.RelayerConfig.MpcConfig.FrostKeysharePath) + propStore := propStore.NewPropStore(db) // wait until executions are done and then stop further executions before exiting exitLock := &sync.RWMutex{} @@ -144,6 +153,7 @@ func Run() error { log.Info().Str("domain", config.String()).Msgf("Registering EVM domain") bridgeAddress := common.HexToAddress(config.Bridge) + frostAddress := common.HexToAddress(config.FrostKeygen) dummyGasPricer := gas.NewStaticGasPriceDeterminant(client, nil) t := monitored.NewMonitoredTransactor(transaction.NewTransaction, dummyGasPricer, client, config.MaxGasPrice, config.GasIncreasePercentage) go t.Monitor(ctx, time.Minute*3, time.Minute*10, time.Minute) @@ -185,6 +195,7 @@ func Run() error { l := log.With().Str("chain", fmt.Sprintf("%v", config.GeneralChainConfig.Name)).Uint8("domainID", *config.GeneralChainConfig.Id) eventHandlers = append(eventHandlers, hubEventHandlers.NewDepositEventHandler(depositListener, depositHandler, bridgeAddress, *config.GeneralChainConfig.Id, msgChan)) eventHandlers = append(eventHandlers, hubEventHandlers.NewKeygenEventHandler(l, tssListener, coordinator, host, communication, keyshareStore, bridgeAddress, networkTopology.Threshold)) + eventHandlers = append(eventHandlers, hubEventHandlers.NewFrostKeygenEventHandler(l, tssListener, coordinator, host, communication, frostKeyshareStore, frostAddress, networkTopology.Threshold)) eventHandlers = append(eventHandlers, hubEventHandlers.NewRefreshEventHandler(l, nil, nil, tssListener, coordinator, host, communication, connectionGate, keyshareStore, bridgeAddress)) eventHandlers = append(eventHandlers, hubEventHandlers.NewRetryEventHandler(l, tssListener, depositHandler, bridgeAddress, *config.GeneralChainConfig.Id, config.BlockConfirmations, msgChan)) evmListener := listener.NewEVMListener(client, eventHandlers, blockstore, sygmaMetrics, *config.GeneralChainConfig.Id, config.BlockRetryInterval, config.BlockConfirmations, config.BlockInterval) @@ -232,6 +243,53 @@ func Run() error { substrateChain := coreSubstrate.NewSubstrateChain(substrateListener, mh, sExecutor, *config.GeneralChainConfig.Id, config.StartBlock) chains[*config.GeneralChainConfig.Id] = substrateChain } + case "btc": + { + log.Info().Msgf("Registering btc domain") + time.Sleep(time.Second * 5) + + config, err := btcConfig.NewBtcConfig(chainConfig) + if err != nil { + panic(err) + } + + conn, err := btcConnection.NewBtcConnection( + config.GeneralChainConfig.Endpoint, + config.Username, + config.Password, + true) + if err != nil { + panic(err) + } + + l := log.With().Str("chain", fmt.Sprintf("%v", config.GeneralChainConfig.Name)).Uint8("domainID", *config.GeneralChainConfig.Id) + depositHandler := &btcListener.BtcDepositHandler{} + eventHandlers := make([]btcListener.EventHandler, 0) + resources := make(map[[32]byte]btcConfig.Resource) + for _, resource := range config.Resources { + resources[resource.ResourceID] = resource + eventHandlers = append(eventHandlers, btcListener.NewFungibleTransferEventHandler(l, *config.GeneralChainConfig.Id, depositHandler, msgChan, conn, resource)) + } + listener := btcListener.NewBtcListener(conn, eventHandlers, config, blockstore) + + mempool := mempool.NewMempoolAPI(config.MempoolUrl) + mh := &btcExecutor.BtcMessageHandler{} + executor := btcExecutor.NewExecutor( + propStore, + host, + communication, + coordinator, + frostKeyshareStore, + conn, + mempool, + resources, + config.Network, + exitLock) + + btcChain := btc.NewBtcChain(listener, executor, mh, *config.GeneralChainConfig.Id) + chains[*config.GeneralChainConfig.Id] = btcChain + + } default: panic(fmt.Errorf("type '%s' not recognized", chainConfig["type"])) } diff --git a/example/cfg/config_evm-evm_1.json b/example/cfg/config_evm-evm_1.json index e86b4276..f681fd53 100644 --- a/example/cfg/config_evm-evm_1.json +++ b/example/cfg/config_evm-evm_1.json @@ -3,6 +3,7 @@ "mpcConfig": { "port": "9000", "keysharePath": "/cfg/keyshares/0.keyshare", + "frostKeysharePath": "/cfg/keyshares/0-frost.keyshare", "key": "CAASpwkwggSjAgEAAoIBAQDVGgUufcOR+u/KjuifMbWbEy4F18250ua3/+PO2eSn/mNRhPp2KCWZJcbVvcvfmsrUG9rJC9DMeUZqttTGSF4/ZaNzzvW0b3Ij2NsI9F+R9HKfsbak8NT8z4nLlWF6V2JuZAD8papG+S60k97278kTPIDZZ+S1ZzH/ViFG/5eu26bNylU27AjL4tTRivXSW6hqlem/RnuPdHNtHqBmw2pAAsV/CZJjmibsk6wQJAMH5/o453rW5v2Ntc0rkGH0leszk9DUJoALr/623MP4re4ZsNPIoZE+ieU4sbM0d8vkbxs4BRcnAa075X8dX8KTl88MiA+AxW9nkW+X2Sn0XI1/AgMBAAECggEBAMNghduoJoRSo0L9Xz2FX9F79jgZMV7rg+iyzXQ6xa9YRkrZNqDaEg6lWfVhe+fYjZmGqEKneJnfnrX8Rnw8oVxSnVdyKkdx3h4LllZRZsX0bpsHXkM/IqdeyCFFJgf60h4Pxe/dG47SqwWYhVW1Zo8ia6fn3wKKSIanuv7TG4iN4Zd1T+joPF9ACq5zYqVBN0nwHTTrMdBjwWOo8jguLXwRgNw1j2qTcTfDvhfkHKXNVm2QMul8DOWZOpHPnFdp1pPu6lAuHyDUvfbA3RLIh2T//iuSWNiHw1bYXhQMcOeB0rqhISEE/qFNHC/9lzCqVoDTP4YfU0SV0ezx4Vgu9zECgYEA8K++vpfezsBEEvtl5iG2NfvqypC6rQrrt4WWEgvskRrNRY4NUeXC3CJpH6ItOwruIv0NTQz10RoH+d6m7isOoUHCSHKFVcePdDcFJXlQf8WN99vFATB39ItfL+fepLqKSZ8+dh59pq+76ewdvStZXhV3Idjcx3L/D2RIVj0nzUUCgYEA4qj6cCOUfEyeE2YIOJ0+iP3GUPu90pNWTpS5R8UE7GBgHCSqBlNpIcoaOzDVJWN30OYVoyBBxfYaTGx5L5adMCtQCUaSuYlCb0Sd8L6v5cnKNXm9Mdd7L19y9RamGdzGS4pkkatG8Gzod3DESXbcEYg5djn/8meeirFoDV6IsfMCgYAH7sLqpTbCubOErKR/IT1QKi1i38JHUcTTF6QKlDoHzkpVsIjf2iLB/qBYWpADEiknHhACKhsv+RuqMJxv3RtuVSyCFsQuP5WKzwVsZsMwcuJq+ONVVrOda7qHaaz84OkN5CG64uZhSAl5fD6+rV8UqsBybSNZr4CYkUWREhLtwQKBgHEGIhvZIin5arnxnxfcEVrucP3hCn7+yYLV1q5bKGFWjZZ7Ee2lmj8nMH1jlGXYe97HXPLDGwlD90k0rhl02V0zu+1kK7YpI9+oL7nk3IGRZivUUOuRr/OnfQOKD7nFxXvVvuCEsBMju6gTq02W35Y+f6jcsyyFTyGJ5YEFKtTRAoGAG/7OBEetoNyWTvMQ6w5HjQkz66n05DoUKFJ44T6ia1vlZCacZ5F7T1S0eMo3zlUp8F/Kz1VxmG3UlsiX3RMf9qiOh+HFIGI6lAtNojHn4jdXpCxTRHecu9GDmds8/7MLemJvoJ4oo3I1n/PyEA9fUMJn07MTGMAABw2IkcOS0dw=", "topologyConfiguration": { "path": ".", @@ -14,7 +15,7 @@ "opentelemetryCollectorURL": "http://otel-collector:4318" }, "domains": [ - { + { "id": 1, "name": "evm1", "type": "evm", @@ -31,15 +32,15 @@ }, { "type": "erc1155", - "address": "0x156fA85e1df5d69B0F138dcEbAa5a14ca640FaED" + "address": "0x9Fd58882b82EFaD2867f7eaB43539907bc07C360" }, { "type": "permissionedGeneric", - "address": "0xa4640d1315Be1f88aC4F81546AA2C785cf247C31" + "address": "0xF956Ba663bd563f585e00D5973E06b443E5C4D65" }, { "type": "permissionlessGeneric", - "address": "0xa2451c8553371E754F5e93A440aDcCa1c0DcF395" + "address": "0x156fA85e1df5d69B0F138dcEbAa5a14ca640FaED" } ], "gasLimit": 9000000, @@ -64,15 +65,15 @@ }, { "type": "erc1155", - "address": "0x156fA85e1df5d69B0F138dcEbAa5a14ca640FaED" + "address": "0x9Fd58882b82EFaD2867f7eaB43539907bc07C360" }, { "type": "permissionedGeneric", - "address": "0xa4640d1315Be1f88aC4F81546AA2C785cf247C31" + "address": "0xF956Ba663bd563f585e00D5973E06b443E5C4D65" }, { "type": "permissionlessGeneric", - "address": "0xa2451c8553371E754F5e93A440aDcCa1c0DcF395" + "address": "0x156fA85e1df5d69B0F138dcEbAa5a14ca640FaED" } ], "gasLimit": 9000000, @@ -91,6 +92,26 @@ "blockConfirmations": 2, "key": "//Alice", "substrateNetwork": 0 + }, + { + "id": 4, + "name": "bitcoin", + "type": "btc", + "startBlock": 100, + "endpoint": "bitcoin:18443", + "mempoolUrl": "http://mempool-stub:8882", + "blockConfirmations": 1, + "network": "regtest", + "username": "user", + "password": "password", + "resources": [ + { + "address": "bcrt1pdf5c3q35ssem2l25n435fa69qr7dzwkc6gsqehuflr3euh905l2sjyr5ek", + "resourceId": "0x0000000000000000000000000000000000000000000000000000000000001000", + "script": "51206a698882348433b57d549d6344f74500fcd13ad8d2200cdf89f8e39e5cafa7d5", + "tweak": "c82aa6ae534bb28aaafeb3660c31d6a52e187d8f05d48bb6bdb9b733a9b42212" + } + ] } ] } diff --git a/example/cfg/config_evm-evm_2.json b/example/cfg/config_evm-evm_2.json index ee7e538a..6ff0ebf4 100644 --- a/example/cfg/config_evm-evm_2.json +++ b/example/cfg/config_evm-evm_2.json @@ -3,6 +3,7 @@ "mpcConfig": { "port": "9001", "keysharePath": "/cfg/keyshares/1.keyshare", + "frostKeysharePath": "/cfg/keyshares/1-frost.keyshare", "key": "CAASpwkwggSjAgEAAoIBAQC89zG/nTK/iWXllnMv65fyrhndM/hhe4IAUnC1kgdzazgUDRCsDHvmPOJ2TccgBjiuG5alxo+tw9y20PYJJoZB7ctDPxBKRJ/6l+0UrWUZE4LxxbKPW4RIRvosVr78OFTrRE63eTsJwUEDNj8EanN+biftSlSzN3DAh/ZSEZtqXUEul2ZxZcuSzgo7gjglGSqTlMLEMHhTu09ceXUECNeky2v6pAKQ/7khaMS96nYTBcyT5ZrEss0fQV13txHoMie+LbnjFM82ezZwcN4GGiHyxyU88/1WP/SKQhW6Ug5Zvd5kkDUuZzKMDRaj3gwDf47uYUGEGDMoL8m2r3OUIAVJAgMBAAECggEAIW3xxR5APhZGiolFM3MQXIRkWve4tzYsV3Y+LkD5RHfxYcq16loDCUY4Igm8cnPnTxXeXtFz1Z/SyhzJifDgy4UP8oOTlC/zxVdfP8s1GWyUO8Vnw2jDTC4SUAnAm8oza7OX4Wl3AlDH+ZN9LoDOkc1XDumZdmMWdVkYQIiQ4A30+AVcuKpfWZa+e034vtHV6/0Ys+zX/jbiRgw5bnZAsFiiy577V0RGzMqcILtrZfg9FHmyRhrfQH+Kh1HlYrHIUqaWaZHl8vROBHJs0ePqPoXmVSCu9dzJLtIzZlAtIUi7CX1SkIXFR4ZF+RORGQK3v0qNLQK55hPe/DndhpewoQKBgQDC4Y6vgbTLurYEgT4kDlrY0rZ1Yjkxi4LwpEfiEN/NHeO5IwePc4SwZ7y4JVNcN0KJFPlitdELXjKJJ9rjmvgUHJ2IQu1drEk5JJXdKq7s6lEROhggj1U+tRBc5znmOPd/PsFjETJ0ux4/vSBM6fQgWK+yGfL2QZdJbwZ7N8QtDQKBgQD4OrMz9G/GCdxnXbHnu+RTTYrHa6WrUBViRqUyy/PYnLD1TGvaARf64R7kHXrhIBZlLLjuiOq8VhVBD5jGpK798Egk52DoRPIRPYmhmGkg7iNj62MUrCk4BwVsgOBZjkvzb0iSIOh67ZpqR7p5iKhAkasaOLtR/ertAGjxSPICLQKBgQC2PMD2ZF+SKB5v5gAbLYVMvva6nEiJiUHAEI/b2hW+tRWvmg9G2Sqa39c9iEIEuPQyRnfTE9zROZweYDOzpcYY4I17z8IyV5r9obW25WRAzflTF7VaNU7s3drYVa3yGRFwX1nubzV0rUpjJlOfOSP+X5ClBtkJ6Vo05Gr2LLb5vQKBgE0wG6dtPBCN4m52ESspHw/UTfJDBAooZe3gX+BS0WmrF8+/Ss5Ihsdpl2yBykMU8kHVzU/HXY7hCczoZ65laVDnldd754NyZP99bj3EofQueOzKNk4oVszoSgR6cskGWZHCKgPencwT9G9WDKNcHk2tvlHyEDWnypSWCwv0jeSlAoGACso4W3vBXaXCUbNqOQ+T0GZGvbvGUDE5XC2Xe7rbZAsoAgrjInqZhwqqjnRV4QnjtdXrA+PjtubS9OKeU6aguFp0f/8VQzZ4lGKFHj+6Sa84+5pEvc+V9lRg1FKTLyDY8d8IXxjqc/IKfc+yosyXFdwkYwddSVmH5fjwHaH/bBg=", "topologyConfiguration": { "path": ".", @@ -14,7 +15,7 @@ "opentelemetryCollectorURL": "http://otel-collector:4318" }, "domains": [ - { + { "id": 1, "name": "evm1", "type": "evm", @@ -31,15 +32,15 @@ }, { "type": "erc1155", - "address": "0x156fA85e1df5d69B0F138dcEbAa5a14ca640FaED" + "address": "0x9Fd58882b82EFaD2867f7eaB43539907bc07C360" }, { "type": "permissionedGeneric", - "address": "0xa4640d1315Be1f88aC4F81546AA2C785cf247C31" + "address": "0xF956Ba663bd563f585e00D5973E06b443E5C4D65" }, { "type": "permissionlessGeneric", - "address": "0xa2451c8553371E754F5e93A440aDcCa1c0DcF395" + "address": "0x156fA85e1df5d69B0F138dcEbAa5a14ca640FaED" } ], "gasLimit": 9000000, @@ -64,15 +65,15 @@ }, { "type": "erc1155", - "address": "0x156fA85e1df5d69B0F138dcEbAa5a14ca640FaED" + "address": "0x9Fd58882b82EFaD2867f7eaB43539907bc07C360" }, { "type": "permissionedGeneric", - "address": "0xa4640d1315Be1f88aC4F81546AA2C785cf247C31" + "address": "0xF956Ba663bd563f585e00D5973E06b443E5C4D65" }, { "type": "permissionlessGeneric", - "address": "0xa2451c8553371E754F5e93A440aDcCa1c0DcF395" + "address": "0x156fA85e1df5d69B0F138dcEbAa5a14ca640FaED" } ], "gasLimit": 9000000, @@ -92,6 +93,28 @@ "blockConfirmations": 2, "key": "//Bob", "substrateNetwork": 0 + }, + { + "id": 4, + "chainID": 5, + "name": "bitcoin", + "type": "btc", + "startBlock": 100, + "endpoint": "bitcoin:18443", + "mempoolUrl": "http://mempool-stub:8882", + "blockConfirmations": 1, + "address": "bcrt1pdf5c3q35ssem2l25n435fa69qr7dzwkc6gsqehuflr3euh905l2sjyr5ek", + "network": "regtest", + "username": "user", + "password": "password", + "resources": [ + { + "address": "bcrt1pdf5c3q35ssem2l25n435fa69qr7dzwkc6gsqehuflr3euh905l2sjyr5ek", + "resourceId": "0x0000000000000000000000000000000000000000000000000000000000001000", + "script": "51206a698882348433b57d549d6344f74500fcd13ad8d2200cdf89f8e39e5cafa7d5", + "tweak": "c82aa6ae534bb28aaafeb3660c31d6a52e187d8f05d48bb6bdb9b733a9b42212" + } + ] } ] } diff --git a/example/cfg/config_evm-evm_3.json b/example/cfg/config_evm-evm_3.json index 61d55218..72884292 100644 --- a/example/cfg/config_evm-evm_3.json +++ b/example/cfg/config_evm-evm_3.json @@ -3,6 +3,7 @@ "mpcConfig": { "port": "9002", "keysharePath": "/cfg/keyshares/2.keyshare", + "frostKeysharePath": "/cfg/keyshares/2-frost.keyshare", "key": "CAASpwkwggSjAgEAAoIBAQDZfFDUCVF9APeLcJ7FRNWvqdwiDPgq2YDIKD/wGuHWz0CbsUH4zmp9qPGeU/X9EzFwrk46Bb5Vt2GOfQcizyWYYJQq0J/vSTlPB9+b2lOJEejQ746g+3zdTHXmh7MYSiNXWuaeWfHHX9QllxwRZ22vos44+3ZdFt7W5smhhznghjA9wDauaUWmLNBRnodIji1ni197fI0T5k9pd8nXO0itIXlj5wEY7bca0i6uvIrZ450lmcyGZQQs2dPhUnhODvWkvWhsPHgooPiiceKkBXYLJMRutoDBvCnXj/WEvK9R2As+Zbrql+KxxnfsVaSsp7OD6v2PFEhcJHc2UtBrzLQzAgMBAAECggEADcdKd6R/fSPqivL/nA/YWeCxCZc4wb414nhqVJPfavSGa1a3j0PEi2GArT80fmwQ3cqtOg/TFysfCx/wq8vj3duAh5XgDBDfiJo/GGikkjKIRXRn3NsR9hM6BPjj584KnyYrJi0cnDz9+8OvC4s5776owgNsZElmpMbKT2l5yTeZNThW9C0GTbVGg6uC0llTkakf9ra7PYyZDnRpSWiirkO6HshyHVWudICcXFwT3YrTec3eVl+Kbs/K3/5EmKep/lhJS5Gw95bbFjpzYYLYd9lN84dSdxVTM7m6+rwSW76MTrGwO8fz7xihfniANKbb4Mn1TW4zdfz4mfJvxfAiQQKBgQDfROMgr34tVOAhwUikMBGG5mG3UiniVFMAVscnov6S0IjtVPFR3xU4YmP672gIEdApZENjeLC4Hrguwlpxv4mnEK4VXf14fjjIjbCLKFjJl6TpVXlA0zvVjQOr89hlQhygAeJRGxxh3x/+aqH2uX5FqdVRX9xKofqgx7Le0oOjVQKBgQD5XmEFcJ2AOeVUu53+2ZZtmIyzhCmbMYcQPcJoIlyFGN8PHB6RxHaYunYKgCqR+P0xX5cEqTX5SivmPnd++kf62Pmkss8+OvEl5wjDwVU+yDrvhHGomayU18708aDkdkYFTWhZsGVTAjb2WxraBnTdWXpqIDR7vhU1NDu057gJZwKBgQDKbFBoX/PL8bnLoMvVYGdjoRhS9sFWnBEsq4CgpRydy0d5tZudGN7KYho3djshpdTO0kJuuwutwtxGOJt7qaaJu4u5f5/BUN72pc+8OvZUv5Iaow3uAhWrhdAgZFJ7a7c8MrkXw7a13W/KYK87W+3qBxKKVpkJaVYr0yKO9tm2iQKBgH9QxFA2svMonAGq2HupM7g4zn1I2MU3gDG2lm78PChoDlp0jNb3a2WGrYWm8hUc/ogaucvTaI8iY09mTTQzLFTSOoLwGAc5qK0lS8BGEPPA3vYytKTT8Nz9YdiFFRQPnqhOYRyJhvHei5kz4n22Fix85pcQYjtbTRCg3mr6c2e5AoGATXLjoKmawM2llIcOx8bzWCXGefZHCcwGEhFzTbrr31Fbjo9ZEyCLOedk7VgU0vV8cAYxHCqG+iZseJ4pwmhPq0beNKUkb12ZZWN5l+YafwKogWJpwMIQqYJyAFSXP68Z1rvP2XHYwwR+MKxD/WwzJPNvVMRUk3VKCBvz11jnNeI=", "topologyConfiguration": { "path": ".", @@ -14,7 +15,7 @@ "opentelemetryCollectorURL": "http://otel-collector:4318" }, "domains": [ - { + { "id": 1, "name": "evm1", "type": "evm", @@ -31,15 +32,15 @@ }, { "type": "erc1155", - "address": "0x156fA85e1df5d69B0F138dcEbAa5a14ca640FaED" + "address": "0x9Fd58882b82EFaD2867f7eaB43539907bc07C360" }, { "type": "permissionedGeneric", - "address": "0xa4640d1315Be1f88aC4F81546AA2C785cf247C31" + "address": "0xF956Ba663bd563f585e00D5973E06b443E5C4D65" }, { "type": "permissionlessGeneric", - "address": "0xa2451c8553371E754F5e93A440aDcCa1c0DcF395" + "address": "0x156fA85e1df5d69B0F138dcEbAa5a14ca640FaED" } ], "gasLimit": 9000000, @@ -64,15 +65,15 @@ }, { "type": "erc1155", - "address": "0x156fA85e1df5d69B0F138dcEbAa5a14ca640FaED" + "address": "0x9Fd58882b82EFaD2867f7eaB43539907bc07C360" }, { "type": "permissionedGeneric", - "address": "0xa4640d1315Be1f88aC4F81546AA2C785cf247C31" + "address": "0xF956Ba663bd563f585e00D5973E06b443E5C4D65" }, { "type": "permissionlessGeneric", - "address": "0xa2451c8553371E754F5e93A440aDcCa1c0DcF395" + "address": "0x156fA85e1df5d69B0F138dcEbAa5a14ca640FaED" } ], "gasLimit": 9000000, @@ -92,6 +93,27 @@ "blockConfirmations": 2, "key": "//Charlie", "substrateNetwork": 0 + }, + { + "id": 4, + "name": "bitcoin", + "type": "btc", + "startBlock": 100, + "endpoint": "bitcoin:18443", + "mempoolUrl": "http://mempool-stub:8882", + "blockConfirmations": 1, + "address": "bcrt1pdf5c3q35ssem2l25n435fa69qr7dzwkc6gsqehuflr3euh905l2sjyr5ek", + "network": "regtest", + "username": "user", + "password": "password", + "resources": [ + { + "address": "bcrt1pdf5c3q35ssem2l25n435fa69qr7dzwkc6gsqehuflr3euh905l2sjyr5ek", + "resourceId": "0x0000000000000000000000000000000000000000000000000000000000001000", + "script": "51206a698882348433b57d549d6344f74500fcd13ad8d2200cdf89f8e39e5cafa7d5", + "tweak": "c82aa6ae534bb28aaafeb3660c31d6a52e187d8f05d48bb6bdb9b733a9b42212" + } + ] } ] } diff --git a/example/cfg/entrypoint/entrypoint.sh b/example/cfg/entrypoint/entrypoint.sh new file mode 100755 index 00000000..90d40630 --- /dev/null +++ b/example/cfg/entrypoint/entrypoint.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# Start bitcoind in the background +bitcoind -regtest -daemon -rpcuser=user -rpcpassword=password -rpcport=18443 -rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0 + +# Wait for bitcoind to start +sleep 5 + +bitcoin-cli -regtest -rpcuser=user -rpcpassword=password loadwallet "test" true + +bitcoin-cli -regtest -rpcuser=user -rpcpassword=password importdescriptors '[{ "desc": "addr(bcrt1pdf5c3q35ssem2l25n435fa69qr7dzwkc6gsqehuflr3euh905l2sjyr5ek)#qyrrxhja", "timestamp":"now", "label": "ray"}]' +bitcoin-cli -regtest -rpcuser=user -rpcpassword=password importdescriptors '[{ "desc": "addr(bcrt1pja8aknn7te4empmghnyqnrtjqn0lyg5zy3p5jsdp4le930wnpnxsrtd3ht)#n807x7zd", "timestamp":"now", "label": "ray"}]' + +# Mine some blocks to fund the wallet (101 blocks to ensure the funds are spendable) +bitcoin-cli -regtest -rpcuser=user -rpcpassword=password generatetoaddress 101 "bcrt1pdf5c3q35ssem2l25n435fa69qr7dzwkc6gsqehuflr3euh905l2sjyr5ek" + +bitcoin-cli -regtest -rpcuser=user -rpcpassword=password listunspent + +# Check balance +BALANCE=$(bitcoin-cli -regtest -rpcuser=user -rpcpassword=password getbalance) +# Check if BALANCE is assigned properly +if [ -z "$BALANCE" ]; then + echo "Failed to retrieve balance" + exit 1 +fi +echo "Wallet Balance: $BALANCE BTC" + +# Keep the container running +tail -f /dev/null \ No newline at end of file diff --git a/example/cfg/keyshares/0-frost.keyshare b/example/cfg/keyshares/0-frost.keyshare new file mode 100755 index 00000000..03c11615 --- /dev/null +++ b/example/cfg/keyshares/0-frost.keyshare @@ -0,0 +1 @@ +{"Key":{"ID":"QmcvEg7jGvuxdsUFRUiE4VdrL2P1Yeju5L83BsJvvXz7zX","Threshold":1,"PrivateShare":"guc3ZBZNfeoJZQlbeoZKXO4rkFVVhc8mRHhma1+Y+pA=","PublicKey":"3NNmUE/J5K0M2xVvb01g2NuRkaOlJ7s8C0BwuGr8yWA=","ChainKey":null,"VerificationShares":{"QmYAYuLUPNwYEBYJaKHcE7NKjUhiUV8txx2xDXHvcYa1xK":"A18VgtOV5WfDW5QR0lIdvodvcDEbElz0G0vHoxyoNkk1","QmcvEg7jGvuxdsUFRUiE4VdrL2P1Yeju5L83BsJvvXz7zX":"AkarNXxFKHYXojxQ6o45zuFzX93JdQMsL/NzA0/Ic/Lm","QmeTuMtdpPB7zKDgmobEwSvxodrf5aFVSmBXX3SQJVjJaT":"A2NE2efE1QiuE4598+tUrU4mDKEHlSzFHEnFBFH1DWRX"}},"Threshold":1,"Peers":["QmeTuMtdpPB7zKDgmobEwSvxodrf5aFVSmBXX3SQJVjJaT","QmYAYuLUPNwYEBYJaKHcE7NKjUhiUV8txx2xDXHvcYa1xK","QmcvEg7jGvuxdsUFRUiE4VdrL2P1Yeju5L83BsJvvXz7zX"]} \ No newline at end of file diff --git a/example/cfg/keyshares/1-frost.keyshare b/example/cfg/keyshares/1-frost.keyshare new file mode 100755 index 00000000..039c93f0 --- /dev/null +++ b/example/cfg/keyshares/1-frost.keyshare @@ -0,0 +1 @@ +{"Key":{"ID":"QmeTuMtdpPB7zKDgmobEwSvxodrf5aFVSmBXX3SQJVjJaT","Threshold":1,"PrivateShare":"dUXvec0uRlWvQ90Wi8B+Hz+VkcbQr9+h0zs4IahNSTc=","PublicKey":"3NNmUE/J5K0M2xVvb01g2NuRkaOlJ7s8C0BwuGr8yWA=","ChainKey":null,"VerificationShares":{"QmYAYuLUPNwYEBYJaKHcE7NKjUhiUV8txx2xDXHvcYa1xK":"A18VgtOV5WfDW5QR0lIdvodvcDEbElz0G0vHoxyoNkk1","QmcvEg7jGvuxdsUFRUiE4VdrL2P1Yeju5L83BsJvvXz7zX":"AkarNXxFKHYXojxQ6o45zuFzX93JdQMsL/NzA0/Ic/Lm","QmeTuMtdpPB7zKDgmobEwSvxodrf5aFVSmBXX3SQJVjJaT":"A2NE2efE1QiuE4598+tUrU4mDKEHlSzFHEnFBFH1DWRX"}},"Threshold":1,"Peers":["QmeTuMtdpPB7zKDgmobEwSvxodrf5aFVSmBXX3SQJVjJaT","QmcvEg7jGvuxdsUFRUiE4VdrL2P1Yeju5L83BsJvvXz7zX","QmYAYuLUPNwYEBYJaKHcE7NKjUhiUV8txx2xDXHvcYa1xK"]} \ No newline at end of file diff --git a/example/cfg/keyshares/2-frost.keyshare b/example/cfg/keyshares/2-frost.keyshare new file mode 100755 index 00000000..30fdc3ae --- /dev/null +++ b/example/cfg/keyshares/2-frost.keyshare @@ -0,0 +1 @@ +{"Key":{"ID":"QmYAYuLUPNwYEBYJaKHcE7NKjUhiUV8txx2xDXHvcYa1xK","Threshold":1,"PrivateShare":"koKwlrcSJO4RWtpYea1VGrq0P1mMF9GZgTREhqXk9aw=","PublicKey":"3NNmUE/J5K0M2xVvb01g2NuRkaOlJ7s8C0BwuGr8yWA=","ChainKey":null,"VerificationShares":{"QmYAYuLUPNwYEBYJaKHcE7NKjUhiUV8txx2xDXHvcYa1xK":"A18VgtOV5WfDW5QR0lIdvodvcDEbElz0G0vHoxyoNkk1","QmcvEg7jGvuxdsUFRUiE4VdrL2P1Yeju5L83BsJvvXz7zX":"AkarNXxFKHYXojxQ6o45zuFzX93JdQMsL/NzA0/Ic/Lm","QmeTuMtdpPB7zKDgmobEwSvxodrf5aFVSmBXX3SQJVjJaT":"A2NE2efE1QiuE4598+tUrU4mDKEHlSzFHEnFBFH1DWRX"}},"Threshold":1,"Peers":["QmcvEg7jGvuxdsUFRUiE4VdrL2P1Yeju5L83BsJvvXz7zX","QmYAYuLUPNwYEBYJaKHcE7NKjUhiUV8txx2xDXHvcYa1xK","QmeTuMtdpPB7zKDgmobEwSvxodrf5aFVSmBXX3SQJVjJaT"]} \ No newline at end of file diff --git a/example/cfg/stubs/mempool.yml b/example/cfg/stubs/mempool.yml new file mode 100644 index 00000000..5004c87e --- /dev/null +++ b/example/cfg/stubs/mempool.yml @@ -0,0 +1,17 @@ +- request: + url: /api/address/bcrt1pdf5c3q35ssem2l25n435fa69qr7dzwkc6gsqehuflr3euh905l2sjyr5ek/utxo + method: GET + response: + headers: + content-type: application/json + body: > + [{"txid":"16e57c77dcb150039a237721ee8d7e2d8d5515fc2a40d2cd3e959e88b436d903","vout":0,"value":5000000000}] + +- request: + url: /api/v1/fees/recommended + method: GET + response: + headers: + content-type: application/json + body: > + {"fastestFee":15,"halfHourFee":10,"hourFee":1,"economyFee":10,"minimumFee":5} \ No newline at end of file diff --git a/example/docker-compose.yml b/example/docker-compose.yml index 768774a3..632962cb 100644 --- a/example/docker-compose.yml +++ b/example/docker-compose.yml @@ -1,10 +1,35 @@ # Copyright 2020 ChainSafe Systems # SPDX-License-Identifier: LGPL-3.0-only -version: '3' services: + mempool-stub: + image: ghcr.io/sygmaprotocol/beacon-api-stub + volumes: + - "./cfg/stubs:/stubs" + ports: + - 8882:8882 + environment: + - STUB_DATA=/stubs/mempool.yml + + bitcoin: +# image: ruimarinho/bitcoin-core:latest + image: ghcr.io/sygmaprotocol/bitcoin + container_name: bitcoin + ports: + - "18443:18443" + - "18332:18332" + environment: + BITCOIN_RPC_USER: user + BITCOIN_RPC_PASSWORD: password + BITCOIN_RPC_PORT: 18443 + BITCOIN_NETWORK: regtest + BITCOIN_EXTRA_ARGS: "-rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0" + volumes: + - ./cfg/entrypoint/entrypoint.sh:/cfg/entrypoint/entrypoint.sh + entrypoint: /cfg/entrypoint/entrypoint.sh + evm1-1: - image: ghcr.io/sygmaprotocol/sygma-solidity:evm1-v2.5.3 + image: ghcr.io/sygmaprotocol/sygma-solidity:evm1-v2.7.0 container_name: evm1-1 command: ganache-cli --chainId 1337 -d --db data/ --blockTime 2 > /dev/null logging: @@ -12,9 +37,8 @@ services: ports: - "8545:8545" - # SECOND CHAIN evm2-1: - image: ghcr.io/sygmaprotocol/sygma-solidity:evm2-v2.5.3 + image: ghcr.io/sygmaprotocol/sygma-solidity:evm2-v2.7.0 command: ganache-cli --chainId 1338 -d --db data/ --blockTime 2 > /dev/null container_name: evm2-1 logging: diff --git a/go.mod b/go.mod index b7cdf50a..c015c8c5 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,11 @@ go 1.19 require ( github.com/binance-chain/tss-lib v0.0.0-00010101000000-000000000000 + github.com/btcsuite/btcd/btcutil v1.1.5 + github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 github.com/centrifuge/go-substrate-rpc-client/v4 v4.1.0 github.com/creasty/defaults v1.6.0 + github.com/deckarep/golang-set/v2 v2.1.0 github.com/ethereum/go-ethereum v1.13.4 github.com/golang/mock v1.6.0 github.com/imdario/mergo v0.3.12 @@ -18,26 +21,34 @@ require ( github.com/spf13/viper v1.9.0 github.com/stretchr/testify v1.8.4 github.com/sygmaprotocol/sygma-core v0.0.0-20240411120252-bf0131a81565 + github.com/taurusgroup/multi-party-sig v0.6.0-alpha-2021-09-21.0.20230619131919-9c7c6ffd7217 go.opentelemetry.io/otel v1.16.0 go.opentelemetry.io/otel/metric v1.16.0 + go.uber.org/mock v0.3.0 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 ) require ( github.com/Microsoft/go-winio v0.6.1 // indirect github.com/bits-and-blooms/bitset v1.7.0 // indirect + github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect + github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect + github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/consensys/bavard v0.1.13 // indirect github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/crate-crypto/go-kzg-4844 v0.3.0 // indirect - github.com/deckarep/golang-set/v2 v2.1.0 // indirect + github.com/cronokirby/saferith v0.33.0 // indirect github.com/ethereum/c-kzg-4844 v0.3.1 // indirect + github.com/fxamacker/cbor/v2 v2.4.0 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect github.com/holiman/uint256 v1.2.3 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/supranational/blst v0.3.11 // indirect + github.com/x448/float16 v0.8.4 // indirect + github.com/zeebo/blake3 v0.2.3 // indirect go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) @@ -49,7 +60,7 @@ require ( github.com/agl/ed25519 v0.0.0-20200305024217-f36fc4b53d43 // indirect github.com/benbjohnson/clock v1.3.0 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/btcsuite/btcd v0.22.0-beta.0.20220201204404-81fbd9b67e54 // indirect + github.com/btcsuite/btcd v0.24.0 github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect github.com/btcsuite/btcutil v1.0.3-0.20211129182920-9c4bbabe7acd // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect @@ -60,9 +71,9 @@ require ( github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/deckarep/golang-set v1.8.0 // indirect github.com/decred/base58 v1.0.4 // indirect - github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect + github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect github.com/decred/dcrd/dcrec/edwards/v2 v2.0.2 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/elastic/gosigar v0.14.2 // indirect github.com/flynn/noise v1.0.0 // indirect @@ -89,7 +100,7 @@ require ( github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/klauspost/compress v1.15.15 // indirect - github.com/klauspost/cpuid/v2 v2.2.3 // indirect + github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/koron/go-ssdp v0.0.3 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect @@ -149,7 +160,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect - github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect + github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/vedhavyas/go-subkey v1.0.4 // indirect @@ -161,7 +172,7 @@ require ( go.opentelemetry.io/proto/otlp v0.19.0 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.9.0 // indirect - go.uber.org/zap v1.23.0 // indirect + go.uber.org/zap v1.23.0 golang.org/x/crypto v0.17.0 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.18.0 // indirect @@ -183,5 +194,5 @@ require ( replace ( github.com/agl/ed25519 => github.com/binance-chain/edwards25519 v0.0.0-20200305024217-f36fc4b53d43 github.com/binance-chain/tss-lib => github.com/ChainSafe/threshlib v0.0.0-20230420112309-603112eb4684 - + github.com/taurusgroup/multi-party-sig => github.com/sygmaprotocol/multi-party-sig v0.0.0-20240523153754-9377ba09c35e ) diff --git a/go.sum b/go.sum index 988828ec..3d12dec0 100644 --- a/go.sum +++ b/go.sum @@ -90,17 +90,34 @@ github.com/bits-and-blooms/bitset v1.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHl github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btcd v0.22.0-beta.0.20220201204404-81fbd9b67e54 h1:khJx6kvXopB224O05cs6iwmX/zAh7RlkMf8MiPXVS1I= -github.com/btcsuite/btcd v0.22.0-beta.0.20220201204404-81fbd9b67e54/go.mod h1:vkwesBkYQtKXFYQYi9PyahtopbX53Tvk/O/qp2WI6Gk= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= +github.com/btcsuite/btcd v0.24.0 h1:gL3uHE/IaFj6fcZSu03SvqPMSx7s/dPzfpG/atRwWdo= +github.com/btcsuite/btcd v0.24.0/go.mod h1:K4IDc1593s8jKXIF7yS7yCTSxrknB9z0STzc2j6XgE4= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= +github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v1.0.3-0.20211129182920-9c4bbabe7acd h1:vAwk2PCYxzUUGAXXtw66PyY2IMCwWBnm8GR5aLIxS3Q= github.com/btcsuite/btcutil v1.0.3-0.20211129182920-9c4bbabe7acd/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= @@ -158,6 +175,8 @@ github.com/crate-crypto/go-kzg-4844 v0.3.0 h1:UBlWE0CgyFqqzTI+IFyCzA7A3Zw4iip6uz github.com/crate-crypto/go-kzg-4844 v0.3.0/go.mod h1:SBP7ikXEgDnUPONgm33HtuDZEDtWa3L4QtN1ocJSEQ4= github.com/creasty/defaults v1.6.0 h1:ltuE9cfphUtlrBeomuu8PEyISTXnxqkBIoQfXgv7BSc= github.com/creasty/defaults v1.6.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM= +github.com/cronokirby/saferith v0.33.0 h1:TgoQlfsD4LIwx71+ChfRcIpjkw+RPOapDEVxa+LhwLo= +github.com/cronokirby/saferith v0.33.0/go.mod h1:QKJhjoqUtBsXCAVEjw38mFqoi7DebT7kthcD7UzbnoA= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -170,12 +189,15 @@ github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6 github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/base58 v1.0.4 h1:QJC6B0E0rXOPA8U/kw2rP+qiRJsUaE2Er+pYb3siUeA= github.com/decred/base58 v1.0.4/go.mod h1:jJswKPEdvpFpvf7dsDvFZyLT22xZ9lWqEByX38oGd9E= -github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/edwards/v2 v2.0.2 h1:bX7rtGTMBDJxujZ29GNqtn7YCAdINjHKnA6J6tBBv6s= github.com/decred/dcrd/dcrec/edwards/v2 v2.0.2/go.mod h1:d0H8xGMWbiIQP7gN3v2rByWUcuZPm9YsgmnfoxgbINc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= @@ -210,6 +232,8 @@ github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= +github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= @@ -404,6 +428,7 @@ github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABo github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -421,8 +446,9 @@ github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6 github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= -github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= @@ -570,10 +596,12 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= @@ -729,10 +757,11 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= -github.com/sygmaprotocol/sygma-core v0.0.0-20240410132338-3378ddf94d94 h1:X54V8UoBM3BzQWX52ZaqPxIHDMHf+aKy3tTTrCzPh2k= -github.com/sygmaprotocol/sygma-core v0.0.0-20240410132338-3378ddf94d94/go.mod h1:b4RZCyYr20Mp4WAAj4TkC6gU2KZ0ZWcpSGmKc6n8NKc= +github.com/sygmaprotocol/multi-party-sig v0.0.0-20240523153754-9377ba09c35e h1:IzidTxe1CT6O5qy2gK9NQO/hrC+2wjDGzCxCF+kjCKU= +github.com/sygmaprotocol/multi-party-sig v0.0.0-20240523153754-9377ba09c35e/go.mod h1:roZI3gaKCo15PUSB4LdJpTLTjq8TFsJiOH5kpcN1HpQ= github.com/sygmaprotocol/sygma-core v0.0.0-20240411120252-bf0131a81565 h1:oEd8KmRDSyGvQGg/A0cd4JbWNiVP8GGjK672JWQ0QAk= github.com/sygmaprotocol/sygma-core v0.0.0-20240411120252-bf0131a81565/go.mod h1:b4RZCyYr20Mp4WAAj4TkC6gU2KZ0ZWcpSGmKc6n8NKc= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI= github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= @@ -749,12 +778,20 @@ github.com/vedhavyas/go-subkey v1.0.4 h1:QwjBZx4w7qXC2lmqol2jJfhaNXPI9BsgLZiMiCw github.com/vedhavyas/go-subkey v1.0.4/go.mod h1:aOIil/KS9hJlnr9ZSQKSoXdu/MbnkCxG4x9IOlLsMtI= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= +github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= +github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= +github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= +github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= @@ -866,6 +903,7 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -901,6 +939,7 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -999,11 +1038,13 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1034,9 +1075,9 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= diff --git a/keyshare/keyshare.go b/keyshare/ecdsa.go similarity index 71% rename from keyshare/keyshare.go rename to keyshare/ecdsa.go index fa1887f1..e519d275 100644 --- a/keyshare/keyshare.go +++ b/keyshare/ecdsa.go @@ -15,27 +15,27 @@ import ( // Keyshare stores key received from keygen or resharing // and treshold and peers from current signing committee -type Keyshare struct { +type ECDSAKeyshare struct { Key keygen.LocalPartySaveData Threshold int Peers []peer.ID } -func NewKeyshare(key keygen.LocalPartySaveData, threshold int, peers []peer.ID) Keyshare { - return Keyshare{ +func NewECDSAKeyshare(key keygen.LocalPartySaveData, threshold int, peers []peer.ID) ECDSAKeyshare { + return ECDSAKeyshare{ Key: key, Threshold: threshold, Peers: peers, } } -type KeyshareStore struct { +type ECDSAKeyshareStore struct { mu sync.Mutex path string } -func NewKeyshareStore(filePath string) *KeyshareStore { - return &KeyshareStore{ +func NewECDSAKeyshareStore(filePath string) *ECDSAKeyshareStore { + return &ECDSAKeyshareStore{ path: filePath, } } @@ -43,18 +43,18 @@ func NewKeyshareStore(filePath string) *KeyshareStore { // LockKeyshare locks keyshare from reading and writing to // prevent keygen or resharing being done in parallel with other // tss processes. -func (ks *KeyshareStore) LockKeyshare() { +func (ks *ECDSAKeyshareStore) LockKeyshare() { ks.mu.Lock() } // UnlockKeyshare unlocks keyshare to allow for tss processes to continue -func (ks *KeyshareStore) UnlockKeyshare() { +func (ks *ECDSAKeyshareStore) UnlockKeyshare() { ks.mu.Unlock() } // StoreKeyshare stores keyshare generated by keygen or reshare into file and truncates // old keyshare. -func (ks *KeyshareStore) StoreKeyshare(keyshare Keyshare) error { +func (ks *ECDSAKeyshareStore) StoreKeyshare(keyshare ECDSAKeyshare) error { f, err := os.OpenFile(ks.path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) if err != nil { return err @@ -70,10 +70,10 @@ func (ks *KeyshareStore) StoreKeyshare(keyshare Keyshare) error { return err } -// GetKeyshare fetches current keyshare from file. +// GetECDSAKeyshare fetches current keyshare from file. // Can be a blocking call if keygen or resharing are pending. -func (ks *KeyshareStore) GetKeyshare() (Keyshare, error) { - k := Keyshare{} +func (ks *ECDSAKeyshareStore) GetKeyshare() (ECDSAKeyshare, error) { + k := ECDSAKeyshare{} kb, err := os.ReadFile(ks.path) if err != nil { diff --git a/keyshare/keyshare_test.go b/keyshare/ecdsa_test.go similarity index 56% rename from keyshare/keyshare_test.go rename to keyshare/ecdsa_test.go index 43e328b4..ae531910 100644 --- a/keyshare/keyshare_test.go +++ b/keyshare/ecdsa_test.go @@ -13,37 +13,35 @@ import ( "github.com/stretchr/testify/suite" ) -type KeyshareStoreTestSuite struct { +type ECDSAKeyshareStoreTestSuite struct { suite.Suite - keyshareStore *keyshare.KeyshareStore + keyshareStore *keyshare.ECDSAKeyshareStore path string } -func TestRunKeyshareStoreTestSuite(t *testing.T) { - suite.Run(t, new(KeyshareStoreTestSuite)) +func TestRunECDSAKeyshareStoreTestSuite(t *testing.T) { + suite.Run(t, new(ECDSAKeyshareStoreTestSuite)) } -func (s *KeyshareStoreTestSuite) SetupSuite() {} -func (s *KeyshareStoreTestSuite) TearDownSuite() {} -func (s *KeyshareStoreTestSuite) SetupTest() { +func (s *ECDSAKeyshareStoreTestSuite) SetupTest() { s.path = "share.json" - s.keyshareStore = keyshare.NewKeyshareStore(s.path) + s.keyshareStore = keyshare.NewECDSAKeyshareStore(s.path) } -func (s *KeyshareStoreTestSuite) TearDownTest() { +func (s *ECDSAKeyshareStoreTestSuite) TearDownTest() { os.Remove(s.path) } -func (s *KeyshareStoreTestSuite) Test_RetrieveInvalidFile() { +func (s *ECDSAKeyshareStoreTestSuite) Test_RetrieveInvalidFile() { _, err := s.keyshareStore.GetKeyshare() s.NotNil(err) } -func (s *KeyshareStoreTestSuite) Test_StoreAndRetrieveShare() { +func (s *ECDSAKeyshareStoreTestSuite) Test_StoreAndRetrieveShare() { threshold := 3 peer1, _ := peer.Decode("QmZHPnN3CKiTAp8VaJqszbf8m7v4mPh15M421KpVdYHF54") peer2, _ := peer.Decode("QmcW3oMdSqoEcjbyd51auqC23vhKX6BqfcZcY2HJ3sKAZR") peers := []peer.ID{peer1, peer2} - keyshare := keyshare.NewKeyshare(keygen.NewLocalPartySaveData(5), threshold, peers) + keyshare := keyshare.NewECDSAKeyshare(keygen.NewLocalPartySaveData(5), threshold, peers) err := s.keyshareStore.StoreKeyshare(keyshare) s.Nil(err) diff --git a/keyshare/frost.go b/keyshare/frost.go new file mode 100644 index 00000000..60ac6e79 --- /dev/null +++ b/keyshare/frost.go @@ -0,0 +1,157 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package keyshare + +import ( + "encoding/json" + "fmt" + "os" + "sync" + + "github.com/libp2p/go-libp2p/core/peer" + "github.com/taurusgroup/multi-party-sig/pkg/math/curve" + "github.com/taurusgroup/multi-party-sig/pkg/party" + "github.com/taurusgroup/multi-party-sig/pkg/taproot" + "github.com/taurusgroup/multi-party-sig/protocols/frost" +) + +type FrostKeyshare struct { + Key *frost.TaprootConfig + Threshold int + Peers []peer.ID +} + +type frostKey struct { + ID party.ID + Threshold int + PrivateShare []byte + PublicKey taproot.PublicKey + ChainKey []byte + VerificationShares map[party.ID][]byte +} + +type frostKeyshareStore struct { + Key frostKey + Threshold int + Peers []peer.ID +} + +func NewFrostKeyshare(key *frost.TaprootConfig, threshold int, peers []peer.ID) FrostKeyshare { + return FrostKeyshare{ + Key: key, + Threshold: threshold, + Peers: peers, + } +} + +type FrostKeyshareStore struct { + mu sync.Mutex + path string +} + +func NewFrostKeyshareStore(filePath string) *FrostKeyshareStore { + return &FrostKeyshareStore{ + path: filePath, + } +} + +// LockKeyshare locks keyshare from reading and writing to +// prevent keygen or resharing being done in parallel with other +// tss processes. +func (ks *FrostKeyshareStore) LockKeyshare() { + ks.mu.Lock() +} + +// UnlockKeyshare unlocks keyshare to allow for tss processes to continue +func (ks *FrostKeyshareStore) UnlockKeyshare() { + ks.mu.Unlock() +} + +// StoreFrostKeyshare stores frost keyshare generated by keygen or reshare into file and truncates +// old keyshare. +func (ks *FrostKeyshareStore) StoreKeyshare(keyshare FrostKeyshare) error { + f, err := os.OpenFile(ks.path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) + if err != nil { + return err + } + defer f.Close() + + privateShareBytes, err := keyshare.Key.PrivateShare.MarshalBinary() + if err != nil { + return err + } + verificationShares := make(map[party.ID][]byte) + for id, point := range keyshare.Key.VerificationShares { + pointBytes, err := point.MarshalBinary() + if err != nil { + return err + } + verificationShares[id] = pointBytes + } + fKey := frostKey{ + ID: keyshare.Key.ID, + Threshold: keyshare.Key.Threshold, + PrivateShare: privateShareBytes, + PublicKey: keyshare.Key.PublicKey, + ChainKey: keyshare.Key.ChainKey, + VerificationShares: verificationShares, + } + fStore := frostKeyshareStore{ + Key: fKey, + Threshold: keyshare.Threshold, + Peers: keyshare.Peers, + } + kb, err := json.Marshal(&fStore) + if err != nil { + return err + } + + _, err = f.Write(kb) + return err +} + +// GetFrostKeyshare fetches current keyshare from file. +// Can be a blocking call if keygen or resharing are pending. +func (ks *FrostKeyshareStore) GetKeyshare() (FrostKeyshare, error) { + fStore := frostKeyshareStore{} + k := FrostKeyshare{} + + kb, err := os.ReadFile(ks.path) + if err != nil { + return k, fmt.Errorf("error on reading keyshare file: %s", err) + } + + err = json.Unmarshal(kb, &fStore) + if err != nil { + return k, fmt.Errorf("error on unmarshaling keyshare file: %s", err) + } + k.Threshold = fStore.Threshold + k.Peers = fStore.Peers + + privateShare := &curve.Secp256k1Scalar{} + err = privateShare.UnmarshalBinary(fStore.Key.PrivateShare) + if err != nil { + return k, err + } + verificationShares := make(map[party.ID]*curve.Secp256k1Point) + for id, pointBytes := range fStore.Key.VerificationShares { + point := &curve.Secp256k1Point{} + err := point.UnmarshalBinary(pointBytes) + if err != nil { + return k, err + } + verificationShares[id] = point + } + key := &frost.TaprootConfig{ + ID: fStore.Key.ID, + Threshold: fStore.Key.Threshold, + PrivateShare: privateShare, + PublicKey: fStore.Key.PublicKey, + ChainKey: fStore.Key.ChainKey, + VerificationShares: verificationShares, + } + k.Key = key + + return k, err +} diff --git a/keyshare/frost_test.go b/keyshare/frost_test.go new file mode 100644 index 00000000..da944bef --- /dev/null +++ b/keyshare/frost_test.go @@ -0,0 +1,75 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package keyshare_test + +import ( + "encoding/base64" + "os" + "testing" + + "github.com/ChainSafe/sygma-relayer/keyshare" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/suite" + "github.com/taurusgroup/multi-party-sig/pkg/math/curve" + "github.com/taurusgroup/multi-party-sig/pkg/party" + "github.com/taurusgroup/multi-party-sig/pkg/taproot" + "github.com/taurusgroup/multi-party-sig/protocols/frost" +) + +type FrostKeyshareStoreTestSuite struct { + suite.Suite + keyshareStore *keyshare.FrostKeyshareStore + path string +} + +func TestRunFrostKeyshareStoreTestSuite(t *testing.T) { + suite.Run(t, new(FrostKeyshareStoreTestSuite)) +} + +func (s *FrostKeyshareStoreTestSuite) SetupTest() { + s.path = "share.json" + s.keyshareStore = keyshare.NewFrostKeyshareStore(s.path) +} +func (s *FrostKeyshareStoreTestSuite) TearDownTest() { + os.Remove(s.path) +} + +func (s *FrostKeyshareStoreTestSuite) Test_RetrieveInvalidFile() { + _, err := s.keyshareStore.GetKeyshare() + s.NotNil(err) +} + +func (s *FrostKeyshareStoreTestSuite) Test_StoreAndRetrieveShare() { + privateShare := &curve.Secp256k1Scalar{} + privateShareBytes, _ := base64.StdEncoding.DecodeString("hpUx9M/dN7lAF20Jum3/4sgmfty5W4VNeGoEEB18870=") + _ = privateShare.UnmarshalBinary(privateShareBytes) + + verificationShares := make(map[party.ID]*curve.Secp256k1Point) + point := &curve.Secp256k1Point{} + pointBytes, _ := base64.StdEncoding.DecodeString("Au1e9fpMRj99yTydAjKGGa9H7m/jEpEvySUmTu1xYR9h") + _ = point.UnmarshalBinary(pointBytes) + verificationShares[party.ID("1")] = point + + threshold := 3 + peer1, _ := peer.Decode("QmZHPnN3CKiTAp8VaJqszbf8m7v4mPh15M421KpVdYHF54") + peer2, _ := peer.Decode("QmcW3oMdSqoEcjbyd51auqC23vhKX6BqfcZcY2HJ3sKAZR") + peers := []peer.ID{peer1, peer2} + + keyshare := keyshare.NewFrostKeyshare(&frost.TaprootConfig{ + ID: party.ID(peer1.Pretty()), + Threshold: 1, + PrivateShare: privateShare, + VerificationShares: verificationShares, + PublicKey: taproot.PublicKey{}, + ChainKey: []byte{}, + }, threshold, peers) + + err := s.keyshareStore.StoreKeyshare(keyshare) + s.Nil(err) + + storedKeyshare, err := s.keyshareStore.GetKeyshare() + s.Nil(err) + + s.Equal(keyshare, storedKeyshare) +} diff --git a/relayer/transfer/transfer.go b/relayer/transfer/transfer.go index 84ae88ce..434071f1 100644 --- a/relayer/transfer/transfer.go +++ b/relayer/transfer/transfer.go @@ -1,3 +1,6 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + package transfer import ( diff --git a/store/propstore.go b/store/propstore.go new file mode 100644 index 00000000..463ea45c --- /dev/null +++ b/store/propstore.go @@ -0,0 +1,65 @@ +// Copyright 2021 ChainSafe Systems +// SPDX-License-Identifier: LGPL-3.0-only + +package store + +import ( + "bytes" + "errors" + "fmt" + + "github.com/sygmaprotocol/sygma-core/store" + "github.com/syndtr/goleveldb/leveldb" +) + +type PropStatus string + +var ( + KEY = "source:%d:destination:%d:depositNonce:%d" + MissingProp PropStatus = "missing" + PendingProp PropStatus = "pending" + FailedProp PropStatus = "failed" + ExecutedProp PropStatus = "executed" +) + +type PropStore struct { + db store.KeyValueReaderWriter +} + +func NewPropStore(db store.KeyValueReaderWriter) *PropStore { + return &PropStore{ + db: db, + } +} + +// StorePropStatus stores proposal status per proposal +func (ns *PropStore) StorePropStatus(source, destination uint8, depositNonce uint64, status PropStatus) error { + key := bytes.Buffer{} + keyS := fmt.Sprintf(KEY, source, destination, depositNonce) + key.WriteString(keyS) + + err := ns.db.SetByKey(key.Bytes(), []byte(status)) + if err != nil { + return err + } + + return nil +} + +// GetPropStatus +func (ns *PropStore) PropStatus(source, destination uint8, depositNonce uint64) (PropStatus, error) { + key := bytes.Buffer{} + keyS := fmt.Sprintf(KEY, source, destination, depositNonce) + key.WriteString(keyS) + + v, err := ns.db.GetByKey(key.Bytes()) + if err != nil { + if errors.Is(err, leveldb.ErrNotFound) { + return MissingProp, nil + } + return MissingProp, err + } + + status := PropStatus(string(v)) + return status, nil +} diff --git a/store/propstore_test.go b/store/propstore_test.go new file mode 100644 index 00000000..67f2190d --- /dev/null +++ b/store/propstore_test.go @@ -0,0 +1,75 @@ +package store_test + +import ( + "errors" + "testing" + + "github.com/ChainSafe/sygma-relayer/store" + "github.com/stretchr/testify/suite" + mock_store "github.com/sygmaprotocol/sygma-core/mock" + "github.com/syndtr/goleveldb/leveldb" + "go.uber.org/mock/gomock" +) + +type PropStoreTestSuite struct { + suite.Suite + nonceStore *store.PropStore + keyValueReaderWriter *mock_store.MockKeyValueReaderWriter +} + +func TestRunPropStoreTestSuite(t *testing.T) { + suite.Run(t, new(PropStoreTestSuite)) +} + +func (s *PropStoreTestSuite) SetupTest() { + gomockController := gomock.NewController(s.T()) + s.keyValueReaderWriter = mock_store.NewMockKeyValueReaderWriter(gomockController) + s.nonceStore = store.NewPropStore(s.keyValueReaderWriter) +} + +func (s *PropStoreTestSuite) Test_StorePropStatus_FailedStore() { + key := "source:1:destination:2:depositNonce:3" + s.keyValueReaderWriter.EXPECT().SetByKey([]byte(key), []byte(store.ExecutedProp)).Return(errors.New("error")) + + err := s.nonceStore.StorePropStatus(1, 2, 3, store.ExecutedProp) + + s.NotNil(err) +} + +func (s *PropStoreTestSuite) TestStoreBlock_SuccessfulStore() { + key := "source:1:destination:2:depositNonce:3" + s.keyValueReaderWriter.EXPECT().SetByKey([]byte(key), []byte(store.ExecutedProp)).Return(nil) + + err := s.nonceStore.StorePropStatus(1, 2, 3, store.ExecutedProp) + + s.Nil(err) +} + +func (s *PropStoreTestSuite) Test_GetPropStatus_FailedFetch() { + key := "source:1:destination:2:depositNonce:3" + s.keyValueReaderWriter.EXPECT().GetByKey([]byte(key)).Return(nil, errors.New("error")) + + _, err := s.nonceStore.PropStatus(1, 2, 3) + + s.NotNil(err) +} + +func (s *PropStoreTestSuite) TestGetNonce_NonceNotFound() { + key := "source:1:destination:2:depositNonce:3" + s.keyValueReaderWriter.EXPECT().GetByKey([]byte(key)).Return(nil, leveldb.ErrNotFound) + + status, err := s.nonceStore.PropStatus(1, 2, 3) + + s.Nil(err) + s.Equal(status, store.MissingProp) +} + +func (s *PropStoreTestSuite) TestGetNonce_SuccessfulFetch() { + key := "source:1:destination:2:depositNonce:3" + s.keyValueReaderWriter.EXPECT().GetByKey([]byte(key)).Return([]byte(store.ExecutedProp), nil) + + status, err := s.nonceStore.PropStatus(1, 2, 3) + + s.Nil(err) + s.Equal(status, store.ExecutedProp) +} diff --git a/tss/coordinator.go b/tss/coordinator.go index 64ea6eb8..8a30b2e1 100644 --- a/tss/coordinator.go +++ b/tss/coordinator.go @@ -11,7 +11,8 @@ import ( "github.com/ChainSafe/sygma-relayer/comm" "github.com/ChainSafe/sygma-relayer/comm/elector" - "github.com/ChainSafe/sygma-relayer/tss/common" + "github.com/ChainSafe/sygma-relayer/tss/ecdsa/common" + "github.com/ChainSafe/sygma-relayer/tss/message" "github.com/binance-chain/tss-lib/tss" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" @@ -218,7 +219,7 @@ func (c *Coordinator) retry(ctx context.Context, tssProcess TssProcess, resultCh // broadcastInitiateMsg sends TssInitiateMsg to all peers func (c *Coordinator) broadcastInitiateMsg(sessionID string) { - log.Debug().Msgf("broadcasted initiate message for session: %s", sessionID) + log.Debug().Str("SessionID", sessionID).Msgf("broadcasted initiate message") _ = c.communication.Broadcast( c.host.Peerstore().Peers(), []byte{}, comm.TssInitiateMsg, sessionID, ) @@ -255,7 +256,7 @@ func (c *Coordinator) initiate(ctx context.Context, tssProcess TssProcess, resul } startParams := tssProcess.StartParams(readyMap) - startMsgBytes, err := common.MarshalStartMessage(startParams) + startMsgBytes, err := message.MarshalStartMessage(startParams) if err != nil { return err } @@ -321,7 +322,7 @@ func (c *Coordinator) waitForStart( continue } - msg, err := common.UnmarshalStartMessage(startMsg.Payload) + msg, err := message.UnmarshalStartMessage(startMsg.Payload) if err != nil { return err } diff --git a/tss/common/base.go b/tss/ecdsa/common/base.go similarity index 94% rename from tss/common/base.go rename to tss/ecdsa/common/base.go index 9ada279c..b08b40f4 100644 --- a/tss/common/base.go +++ b/tss/ecdsa/common/base.go @@ -10,6 +10,7 @@ import ( "runtime/debug" "github.com/ChainSafe/sygma-relayer/comm" + "github.com/ChainSafe/sygma-relayer/tss/message" "github.com/binance-chain/tss-lib/tss" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" @@ -58,7 +59,7 @@ func (b *BaseTss) ProcessInboundMessages(ctx context.Context, msgChan chan *comm { b.Log.Debug().Msgf("processed inbound message from %s", wMsg.From) - msg, err := UnmarshalTssMessage(wMsg.Payload) + msg, err := message.UnmarshalTssMessage(wMsg.Payload) if err != nil { return err } @@ -91,7 +92,7 @@ func (b *BaseTss) ProcessOutboundMessages(ctx context.Context, outChn chan tss.M return err } - msgBytes, err := MarshalTssMessage(wireBytes, routing.IsBroadcast) + msgBytes, err := message.MarshalTssMessage(wireBytes, routing.IsBroadcast) if err != nil { return err } diff --git a/tss/common/base_test.go b/tss/ecdsa/common/base_test.go similarity index 96% rename from tss/common/base_test.go rename to tss/ecdsa/common/base_test.go index fd1bffd7..b2dd4176 100644 --- a/tss/common/base_test.go +++ b/tss/ecdsa/common/base_test.go @@ -13,8 +13,9 @@ import ( "github.com/ChainSafe/sygma-relayer/comm" mock_communication "github.com/ChainSafe/sygma-relayer/comm/mock" - "github.com/ChainSafe/sygma-relayer/tss/common" - mock_tss "github.com/ChainSafe/sygma-relayer/tss/common/mock" + "github.com/ChainSafe/sygma-relayer/tss/ecdsa/common" + mock_tss "github.com/ChainSafe/sygma-relayer/tss/ecdsa/common/mock" + "github.com/ChainSafe/sygma-relayer/tss/message" "github.com/binance-chain/tss-lib/tss" "github.com/golang/mock/gomock" "github.com/libp2p/go-libp2p/core/peer" @@ -189,7 +190,7 @@ func (s *BaseTssTestSuite) Test_ProcessInboundMessages_InvalidMessage() { Party: s.mockParty, SID: "sessionID", } - msg, _ := common.MarshalTssMessage([]byte{1}, true) + msg, _ := message.MarshalTssMessage([]byte{1}, true) peer, _ := peer.Decode(peerID) wrappedMsg := &comm.WrappedMessage{ Payload: msg, @@ -217,7 +218,7 @@ func (s *BaseTssTestSuite) Test_ProcessInboundMessages_ValidMessage() { Party: s.mockParty, SID: "sessionID", } - msg, _ := common.MarshalTssMessage([]byte{1}, true) + msg, _ := message.MarshalTssMessage([]byte{1}, true) peer, _ := peer.Decode(peerID) wrappedMsg := &comm.WrappedMessage{ Payload: msg, diff --git a/tss/common/mock/communication.go b/tss/ecdsa/common/mock/communication.go similarity index 98% rename from tss/common/mock/communication.go rename to tss/ecdsa/common/mock/communication.go index 0ca93b52..43eaf7bc 100644 --- a/tss/common/mock/communication.go +++ b/tss/ecdsa/common/mock/communication.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: ./tss/common/base.go +// Source: ./tss/ecdsa/common/base.go // Package mock_tss is a generated GoMock package. package mock_tss diff --git a/tss/common/mock/tss.go b/tss/ecdsa/common/mock/tss.go similarity index 100% rename from tss/common/mock/tss.go rename to tss/ecdsa/common/mock/tss.go diff --git a/tss/common/utils.go b/tss/ecdsa/common/utils.go similarity index 74% rename from tss/common/utils.go rename to tss/ecdsa/common/utils.go index cd73d91d..f9fac5e6 100644 --- a/tss/common/utils.go +++ b/tss/ecdsa/common/utils.go @@ -5,7 +5,6 @@ package common import ( "math/big" - "sort" "github.com/binance-chain/tss-lib/tss" "github.com/libp2p/go-libp2p/core/peer" @@ -17,16 +16,6 @@ func CreatePartyID(peerID string) *tss.PartyID { return tss.NewPartyID(peerID, peerID, key) } -func IsParticipant(party *tss.PartyID, parties tss.SortedPartyIDs) bool { - for _, existingParty := range parties { - if party.Id == existingParty.Id { - return true - } - } - - return false -} - func PeersFromParties(parties []*tss.PartyID) ([]peer.ID, error) { peers := make([]peer.ID, len(parties)) for i, party := range parties { @@ -66,19 +55,6 @@ func PeersFromIDS(peerIDS []string) ([]peer.ID, error) { return peers, nil } -func SortPeersForSession(peers []peer.ID, sessionID string) SortablePeerSlice { - sortedPeers := make(SortablePeerSlice, len(peers)) - for i, p := range peers { - pMsg := PeerMsg{ - ID: p, - SessionID: sessionID, - } - sortedPeers[i] = pMsg - } - sort.Sort(sortedPeers) - return sortedPeers -} - func ExcludePeers(peers peer.IDSlice, excludedPeers peer.IDSlice) peer.IDSlice { includedPeers := make(peer.IDSlice, 0) for _, peer := range peers { diff --git a/tss/common/utils_test.go b/tss/ecdsa/common/utils_test.go similarity index 63% rename from tss/common/utils_test.go rename to tss/ecdsa/common/utils_test.go index 9f2aaa6d..bf6b565a 100644 --- a/tss/common/utils_test.go +++ b/tss/ecdsa/common/utils_test.go @@ -4,44 +4,14 @@ package common_test import ( - "math/big" "testing" - "github.com/ChainSafe/sygma-relayer/tss/common" + "github.com/ChainSafe/sygma-relayer/tss/ecdsa/common" "github.com/binance-chain/tss-lib/tss" "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/suite" ) -type IsParticipantTestSuite struct { - suite.Suite -} - -func TestRunIsParticipantTestSuite(t *testing.T) { - suite.Run(t, new(IsParticipantTestSuite)) -} - -func (s *IsParticipantTestSuite) Test_ValidParticipant() { - party1 := tss.NewPartyID("id1", "id1", big.NewInt(1)) - party2 := tss.NewPartyID("id2", "id2", big.NewInt(2)) - parties := tss.SortedPartyIDs{party1, party2} - - isParticipant := common.IsParticipant(party1, parties) - - s.Equal(true, isParticipant) -} - -func (s *IsParticipantTestSuite) Test_InvalidParticipant() { - party1 := tss.NewPartyID("id1", "id1", big.NewInt(1)) - party2 := tss.NewPartyID("id2", "id2", big.NewInt(2)) - invalidParty := tss.NewPartyID("invalid", "id3", big.NewInt(3)) - parties := tss.SortedPartyIDs{party1, party2} - - isParticipant := common.IsParticipant(invalidParty, parties) - - s.Equal(false, isParticipant) -} - type PeersFromPartiesTestSuite struct { suite.Suite } @@ -110,35 +80,6 @@ func (s *PeersFromIDSTestSuite) Test_ValidIDS() { s.Equal(peers, []peer.ID{peerID1, peerID2}) } -type SortPeersForSessionTestSuite struct { - suite.Suite -} - -func TestRunSortPeersForSessionTestSuite(t *testing.T) { - suite.Run(t, new(SortPeersForSessionTestSuite)) -} - -func (s *SortPeersForSessionTestSuite) Test_NoPeers() { - sortedPeers := common.SortPeersForSession([]peer.ID{}, "sessioniD") - - s.Equal(sortedPeers, common.SortablePeerSlice{}) -} - -func (s *SortPeersForSessionTestSuite) Test_ValidPeers() { - peer1, _ := peer.Decode("QmcW3oMdSqoEcjbyd51auqC23vhKX6BqfcZcY2HJ3sKAZR") - peer2, _ := peer.Decode("QmZHPnN3CKiTAp8VaJqszbf8m7v4mPh15M421KpVdYHF54") - peer3, _ := peer.Decode("QmYayosTHxL2xa4jyrQ2PmbhGbrkSxsGM1kzXLTT8SsLVy") - peers := []peer.ID{peer3, peer2, peer1} - - sortedPeers := common.SortPeersForSession(peers, "sessionID") - - s.Equal(sortedPeers, common.SortablePeerSlice{ - common.PeerMsg{SessionID: "sessionID", ID: peer1}, - common.PeerMsg{SessionID: "sessionID", ID: peer2}, - common.PeerMsg{SessionID: "sessionID", ID: peer3}, - }) -} - type PartiesFromPeersTestSuite struct { suite.Suite } diff --git a/tss/keygen/keygen.go b/tss/ecdsa/keygen/keygen.go similarity index 92% rename from tss/keygen/keygen.go rename to tss/ecdsa/keygen/keygen.go index 89eb35ed..86af0889 100644 --- a/tss/keygen/keygen.go +++ b/tss/ecdsa/keygen/keygen.go @@ -10,7 +10,7 @@ import ( "github.com/ChainSafe/sygma-relayer/comm" "github.com/ChainSafe/sygma-relayer/keyshare" - "github.com/ChainSafe/sygma-relayer/tss/common" + "github.com/ChainSafe/sygma-relayer/tss/ecdsa/common" "github.com/binance-chain/tss-lib/ecdsa/keygen" "github.com/binance-chain/tss-lib/tss" "github.com/ethereum/go-ethereum/crypto" @@ -20,16 +20,16 @@ import ( "github.com/sourcegraph/conc/pool" ) -type SaveDataStorer interface { - StoreKeyshare(keyshare keyshare.Keyshare) error +type ECDSAKeyshareStorer interface { + StoreKeyshare(keyshare keyshare.ECDSAKeyshare) error LockKeyshare() UnlockKeyshare() - GetKeyshare() (keyshare.Keyshare, error) + GetKeyshare() (keyshare.ECDSAKeyshare, error) } type Keygen struct { common.BaseTss - storer SaveDataStorer + storer ECDSAKeyshareStorer threshold int subscriptionID comm.SubscriptionID } @@ -39,7 +39,7 @@ func NewKeygen( threshold int, host host.Host, comm comm.Communication, - storer SaveDataStorer, + storer ECDSAKeyshareStorer, ) *Keygen { partyStore := make(map[string]*tss.PartyID) return &Keygen{ @@ -141,7 +141,7 @@ func (k *Keygen) processEndMessage(ctx context.Context, endChn chan keygen.Local { k.Log.Info().Msgf("Generated key share for address: %s", crypto.PubkeyToAddress(*key.ECDSAPub.ToBtcecPubKey().ToECDSA())) - keyshare := keyshare.NewKeyshare(key, k.threshold, k.Peers) + keyshare := keyshare.NewECDSAKeyshare(key, k.threshold, k.Peers) err := k.storer.StoreKeyshare(keyshare) if err != nil { return err diff --git a/tss/keygen/keygen_test.go b/tss/ecdsa/keygen/keygen_test.go similarity index 85% rename from tss/keygen/keygen_test.go rename to tss/ecdsa/keygen/keygen_test.go index fa349b2e..2ccb6770 100644 --- a/tss/keygen/keygen_test.go +++ b/tss/ecdsa/keygen/keygen_test.go @@ -11,7 +11,7 @@ import ( "github.com/ChainSafe/sygma-relayer/comm" "github.com/ChainSafe/sygma-relayer/comm/elector" "github.com/ChainSafe/sygma-relayer/tss" - "github.com/ChainSafe/sygma-relayer/tss/keygen" + "github.com/ChainSafe/sygma-relayer/tss/ecdsa/keygen" tsstest "github.com/ChainSafe/sygma-relayer/tss/test" "github.com/golang/mock/gomock" "github.com/libp2p/go-libp2p/core/peer" @@ -38,16 +38,16 @@ func (s *KeygenTestSuite) Test_ValidKeygenProcess() { Subscriptions: make(map[comm.SubscriptionID]chan *comm.WrappedMessage), } communicationMap[host.ID()] = &communication - keygen := keygen.NewKeygen("keygen", s.Threshold, host, &communication, s.MockStorer) + keygen := keygen.NewKeygen("keygen", s.Threshold, host, &communication, s.MockECDSAStorer) electorFactory := elector.NewCoordinatorElectorFactory(host, s.BullyConfig) coordinators = append(coordinators, tss.NewCoordinator(host, &communication, electorFactory)) processes = append(processes, keygen) } tsstest.SetupCommunication(communicationMap) - s.MockStorer.EXPECT().LockKeyshare().Times(3) - s.MockStorer.EXPECT().UnlockKeyshare().Times(3) - s.MockStorer.EXPECT().StoreKeyshare(gomock.Any()).Times(3) + s.MockECDSAStorer.EXPECT().LockKeyshare().Times(3) + s.MockECDSAStorer.EXPECT().UnlockKeyshare().Times(3) + s.MockECDSAStorer.EXPECT().StoreKeyshare(gomock.Any()).Times(3) pool := pool.New().WithContext(context.Background()).WithCancelOnError() for i, coordinator := range coordinators { pool.Go(func(ctx context.Context) error { return coordinator.Execute(ctx, processes[i], nil) }) @@ -67,7 +67,7 @@ func (s *KeygenTestSuite) Test_KeygenTimeout() { Subscriptions: make(map[comm.SubscriptionID]chan *comm.WrappedMessage), } communicationMap[host.ID()] = &communication - keygen := keygen.NewKeygen("keygen2", s.Threshold, host, &communication, s.MockStorer) + keygen := keygen.NewKeygen("keygen2", s.Threshold, host, &communication, s.MockECDSAStorer) electorFactory := elector.NewCoordinatorElectorFactory(host, s.BullyConfig) coordinator := tss.NewCoordinator(host, &communication, electorFactory) coordinator.TssTimeout = time.Millisecond @@ -76,9 +76,9 @@ func (s *KeygenTestSuite) Test_KeygenTimeout() { } tsstest.SetupCommunication(communicationMap) - s.MockStorer.EXPECT().LockKeyshare().AnyTimes() - s.MockStorer.EXPECT().UnlockKeyshare().AnyTimes() - s.MockStorer.EXPECT().StoreKeyshare(gomock.Any()).Times(0) + s.MockECDSAStorer.EXPECT().LockKeyshare().AnyTimes() + s.MockECDSAStorer.EXPECT().UnlockKeyshare().AnyTimes() + s.MockECDSAStorer.EXPECT().StoreKeyshare(gomock.Any()).Times(0) pool := pool.New().WithContext(context.Background()) for i, coordinator := range coordinators { pool.Go(func(ctx context.Context) error { return coordinator.Execute(ctx, processes[i], nil) }) diff --git a/tss/resharing/resharing.go b/tss/ecdsa/resharing/resharing.go similarity index 92% rename from tss/resharing/resharing.go rename to tss/ecdsa/resharing/resharing.go index 508c5aba..baf77b4d 100644 --- a/tss/resharing/resharing.go +++ b/tss/ecdsa/resharing/resharing.go @@ -11,7 +11,8 @@ import ( "github.com/ChainSafe/sygma-relayer/comm" "github.com/ChainSafe/sygma-relayer/keyshare" - "github.com/ChainSafe/sygma-relayer/tss/common" + "github.com/ChainSafe/sygma-relayer/tss/ecdsa/common" + "github.com/ChainSafe/sygma-relayer/tss/util" "github.com/binance-chain/tss-lib/ecdsa/keygen" "github.com/binance-chain/tss-lib/ecdsa/resharing" "github.com/binance-chain/tss-lib/tss" @@ -28,15 +29,15 @@ type startParams struct { } type SaveDataStorer interface { - GetKeyshare() (keyshare.Keyshare, error) - StoreKeyshare(keyshare keyshare.Keyshare) error + GetKeyshare() (keyshare.ECDSAKeyshare, error) + StoreKeyshare(keyshare keyshare.ECDSAKeyshare) error LockKeyshare() UnlockKeyshare() } type Resharing struct { common.BaseTss - key keyshare.Keyshare + key keyshare.ECDSAKeyshare subscriptionID comm.SubscriptionID storer SaveDataStorer newThreshold int @@ -50,11 +51,11 @@ func NewResharing( storer SaveDataStorer, ) *Resharing { storer.LockKeyshare() - var key keyshare.Keyshare + var key keyshare.ECDSAKeyshare key, err := storer.GetKeyshare() if err != nil { // empty key for parties that don't have one - key = keyshare.Keyshare{} + key = keyshare.ECDSAKeyshare{} } partyStore := make(map[string]*tss.PartyID) @@ -216,7 +217,7 @@ func (r *Resharing) processEndMessage(ctx context.Context, endChn chan keygen.Lo { r.Log.Info().Msg("Successfully reshared key") - keyshare := keyshare.NewKeyshare(key, r.newThreshold, r.Peers) + keyshare := keyshare.NewECDSAKeyshare(key, r.newThreshold, r.Peers) err := r.storer.StoreKeyshare(keyshare) return err } @@ -234,8 +235,11 @@ func (r *Resharing) sortParties(parties tss.SortedPartyIDs, oldParties tss.Sorte newParties := make(tss.SortedPartyIDs, len(parties)) copy(newParties, oldParties) index := len(oldParties) + oldPeers, _ := common.PeersFromParties(oldParties) + for _, party := range parties { - if !common.IsParticipant(party, oldParties) { + peerID, _ := peer.Decode(party.Id) + if !util.IsParticipant(peerID, oldPeers) { newParties[index] = party newParties[index].Index = index index++ diff --git a/tss/resharing/resharing_test.go b/tss/ecdsa/resharing/resharing_test.go similarity index 83% rename from tss/resharing/resharing_test.go rename to tss/ecdsa/resharing/resharing_test.go index c5e55399..5f2c1495 100644 --- a/tss/resharing/resharing_test.go +++ b/tss/ecdsa/resharing/resharing_test.go @@ -12,7 +12,7 @@ import ( "github.com/ChainSafe/sygma-relayer/comm/elector" "github.com/ChainSafe/sygma-relayer/keyshare" "github.com/ChainSafe/sygma-relayer/tss" - "github.com/ChainSafe/sygma-relayer/tss/resharing" + "github.com/ChainSafe/sygma-relayer/tss/ecdsa/resharing" tsstest "github.com/ChainSafe/sygma-relayer/tss/test" "github.com/golang/mock/gomock" "github.com/libp2p/go-libp2p/core/host" @@ -52,13 +52,13 @@ func (s *ResharingTestSuite) Test_ValidResharingProcess_OldAndNewSubset() { Subscriptions: make(map[comm.SubscriptionID]chan *comm.WrappedMessage), } communicationMap[host.ID()] = &communication - storer := keyshare.NewKeyshareStore(fmt.Sprintf("../test/keyshares/%d.keyshare", i)) + storer := keyshare.NewECDSAKeyshareStore(fmt.Sprintf("../../test/keyshares/%d.keyshare", i)) share, _ := storer.GetKeyshare() - s.MockStorer.EXPECT().LockKeyshare() - s.MockStorer.EXPECT().UnlockKeyshare() - s.MockStorer.EXPECT().GetKeyshare().Return(share, nil) - s.MockStorer.EXPECT().StoreKeyshare(gomock.Any()).Return(nil) - resharing := resharing.NewResharing("resharing2", 1, host, &communication, s.MockStorer) + s.MockECDSAStorer.EXPECT().LockKeyshare() + s.MockECDSAStorer.EXPECT().UnlockKeyshare() + s.MockECDSAStorer.EXPECT().GetKeyshare().Return(share, nil) + s.MockECDSAStorer.EXPECT().StoreKeyshare(gomock.Any()).Return(nil) + resharing := resharing.NewResharing("resharing2", 1, host, &communication, s.MockECDSAStorer) electorFactory := elector.NewCoordinatorElectorFactory(host, s.BullyConfig) coordinators = append(coordinators, tss.NewCoordinator(host, &communication, electorFactory)) processes = append(processes, resharing) @@ -97,16 +97,16 @@ func (s *ResharingTestSuite) Test_InvalidResharingProcess_InvalidOldThreshold_Le Subscriptions: make(map[comm.SubscriptionID]chan *comm.WrappedMessage), } communicationMap[host.ID()] = &communication - storer := keyshare.NewKeyshareStore(fmt.Sprintf("../test/keyshares/%d.keyshare", i)) + storer := keyshare.NewECDSAKeyshareStore(fmt.Sprintf("../../test/keyshares/%d.keyshare", i)) share, _ := storer.GetKeyshare() // set old threshold to invalid value share.Threshold = -1 - s.MockStorer.EXPECT().LockKeyshare().AnyTimes() - s.MockStorer.EXPECT().UnlockKeyshare().AnyTimes() - s.MockStorer.EXPECT().GetKeyshare().Return(share, nil) - resharing := resharing.NewResharing("resharing3", 1, host, &communication, s.MockStorer) + s.MockECDSAStorer.EXPECT().LockKeyshare().AnyTimes() + s.MockECDSAStorer.EXPECT().UnlockKeyshare().AnyTimes() + s.MockECDSAStorer.EXPECT().GetKeyshare().Return(share, nil) + resharing := resharing.NewResharing("resharing3", 1, host, &communication, s.MockECDSAStorer) electorFactory := elector.NewCoordinatorElectorFactory(host, s.BullyConfig) coordinators = append(coordinators, tss.NewCoordinator(host, &communication, electorFactory)) processes = append(processes, resharing) @@ -146,16 +146,16 @@ func (s *ResharingTestSuite) Test_InvalidResharingProcess_InvalidOldThreshold_Bi Subscriptions: make(map[comm.SubscriptionID]chan *comm.WrappedMessage), } communicationMap[host.ID()] = &communication - storer := keyshare.NewKeyshareStore(fmt.Sprintf("../test/keyshares/%d.keyshare", i)) + storer := keyshare.NewECDSAKeyshareStore(fmt.Sprintf("../../test/keyshares/%d.keyshare", i)) share, _ := storer.GetKeyshare() // set old threshold to invalid value share.Threshold = 314 - s.MockStorer.EXPECT().LockKeyshare() - s.MockStorer.EXPECT().UnlockKeyshare().AnyTimes() - s.MockStorer.EXPECT().GetKeyshare().Return(share, nil) - resharing := resharing.NewResharing("resharing4", 1, host, &communication, s.MockStorer) + s.MockECDSAStorer.EXPECT().LockKeyshare() + s.MockECDSAStorer.EXPECT().UnlockKeyshare().AnyTimes() + s.MockECDSAStorer.EXPECT().GetKeyshare().Return(share, nil) + resharing := resharing.NewResharing("resharing4", 1, host, &communication, s.MockECDSAStorer) electorFactory := elector.NewCoordinatorElectorFactory(host, s.BullyConfig) coordinators = append(coordinators, tss.NewCoordinator(host, &communication, electorFactory)) processes = append(processes, resharing) diff --git a/tss/signing/signing.go b/tss/ecdsa/signing/signing.go similarity index 95% rename from tss/signing/signing.go rename to tss/ecdsa/signing/signing.go index 62bbd8a5..426a4b18 100644 --- a/tss/signing/signing.go +++ b/tss/ecdsa/signing/signing.go @@ -23,11 +23,12 @@ import ( "github.com/ChainSafe/sygma-relayer/comm" "github.com/ChainSafe/sygma-relayer/keyshare" errors "github.com/ChainSafe/sygma-relayer/tss" - "github.com/ChainSafe/sygma-relayer/tss/common" + "github.com/ChainSafe/sygma-relayer/tss/ecdsa/common" + "github.com/ChainSafe/sygma-relayer/tss/util" ) type SaveDataFetcher interface { - GetKeyshare() (keyshare.Keyshare, error) + GetKeyshare() (keyshare.ECDSAKeyshare, error) LockKeyshare() UnlockKeyshare() } @@ -35,7 +36,7 @@ type SaveDataFetcher interface { type Signing struct { common.BaseTss coordinator bool - key keyshare.Keyshare + key keyshare.ECDSAKeyshare msg *big.Int resultChn chan interface{} subscriptionID comm.SubscriptionID @@ -88,7 +89,7 @@ func (s *Signing) Run( return err } - if !common.IsParticipant(common.CreatePartyID(s.Host.ID().Pretty()), common.PartiesFromPeers(peerSubset)) { + if !util.IsParticipant(s.Host.ID(), peerSubset) { return &errors.SubsetError{Peer: s.Host.ID()} } @@ -164,7 +165,7 @@ func (s *Signing) StartParams(readyMap map[peer.ID]bool) []byte { peers = append(peers, peer) } - sortedPeers := common.SortPeersForSession(peers, s.SessionID()) + sortedPeers := util.SortPeersForSession(peers, s.SessionID()) peerSubset := []peer.ID{} for _, peer := range sortedPeers { peerSubset = append(peerSubset, peer.ID) diff --git a/tss/signing/signing_test.go b/tss/ecdsa/signing/signing_test.go similarity index 92% rename from tss/signing/signing_test.go rename to tss/ecdsa/signing/signing_test.go index ee947b6d..5f4e0f10 100644 --- a/tss/signing/signing_test.go +++ b/tss/ecdsa/signing/signing_test.go @@ -14,8 +14,8 @@ import ( "github.com/ChainSafe/sygma-relayer/comm/elector" "github.com/ChainSafe/sygma-relayer/keyshare" "github.com/ChainSafe/sygma-relayer/tss" - "github.com/ChainSafe/sygma-relayer/tss/keygen" - "github.com/ChainSafe/sygma-relayer/tss/signing" + "github.com/ChainSafe/sygma-relayer/tss/ecdsa/keygen" + "github.com/ChainSafe/sygma-relayer/tss/ecdsa/signing" tsstest "github.com/ChainSafe/sygma-relayer/tss/test" "github.com/libp2p/go-libp2p/core/peer" "github.com/sourcegraph/conc/pool" @@ -41,7 +41,7 @@ func (s *SigningTestSuite) Test_ValidSigningProcess() { Subscriptions: make(map[comm.SubscriptionID]chan *comm.WrappedMessage), } communicationMap[host.ID()] = &communication - fetcher := keyshare.NewKeyshareStore(fmt.Sprintf("../test/keyshares/%d.keyshare", i)) + fetcher := keyshare.NewECDSAKeyshareStore(fmt.Sprintf("../../test/keyshares/%d.keyshare", i)) msgBytes := []byte("Message") msg := big.NewInt(0) @@ -91,7 +91,7 @@ func (s *SigningTestSuite) Test_SigningTimeout() { Subscriptions: make(map[comm.SubscriptionID]chan *comm.WrappedMessage), } communicationMap[host.ID()] = &communication - fetcher := keyshare.NewKeyshareStore(fmt.Sprintf("../test/keyshares/%d.keyshare", i)) + fetcher := keyshare.NewECDSAKeyshareStore(fmt.Sprintf("../../test/keyshares/%d.keyshare", i)) msgBytes := []byte("Message") msg := big.NewInt(0) @@ -129,14 +129,14 @@ func (s *SigningTestSuite) Test_PendingProcessExists() { Subscriptions: make(map[comm.SubscriptionID]chan *comm.WrappedMessage), } communicationMap[host.ID()] = &communication - keygen := keygen.NewKeygen("keygen3", s.Threshold, host, &communication, s.MockStorer) + keygen := keygen.NewKeygen("keygen3", s.Threshold, host, &communication, s.MockECDSAStorer) electorFactory := elector.NewCoordinatorElectorFactory(host, s.BullyConfig) coordinators = append(coordinators, tss.NewCoordinator(host, &communication, electorFactory)) processes = append(processes, keygen) } tsstest.SetupCommunication(communicationMap) - s.MockStorer.EXPECT().LockKeyshare().AnyTimes() + s.MockECDSAStorer.EXPECT().LockKeyshare().AnyTimes() pool := pool.New().WithContext(context.Background()).WithCancelOnError() for i, coordinator := range coordinators { pool.Go(func(ctx context.Context) error { return coordinator.Execute(ctx, processes[i], nil) }) diff --git a/tss/frost/common/base.go b/tss/frost/common/base.go new file mode 100644 index 00000000..6fad492c --- /dev/null +++ b/tss/frost/common/base.go @@ -0,0 +1,118 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package common + +import ( + "context" + "fmt" + "runtime/debug" + + "github.com/ChainSafe/sygma-relayer/comm" + "github.com/binance-chain/tss-lib/tss" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/rs/zerolog" + "github.com/taurusgroup/multi-party-sig/pkg/protocol" +) + +// BaseTss contains common variables and methods to +// all tss processes. +type BaseFrostTss struct { + Host host.Host + SID string + Communication comm.Communication + Log zerolog.Logger + Peers []peer.ID + Handler *protocol.MultiHandler + Done chan bool + + Cancel context.CancelFunc +} + +// ProcessInboundMessages processes messages from tss parties and updates local party accordingly. +func (k *BaseFrostTss) ProcessInboundMessages(ctx context.Context, msgChan chan *comm.WrappedMessage) (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf(string(debug.Stack())) + } + }() + + for { + select { + case wMsg := <-msgChan: + { + k.Log.Debug().Msgf("processed inbound message from %s", wMsg.From) + + msg := &protocol.Message{} + err := msg.UnmarshalBinary(wMsg.Payload) + if err != nil { + return err + } + if !k.Handler.CanAccept(msg) { + continue + } + go k.Handler.Accept(msg) + } + case <-ctx.Done(): + return nil + } + } +} + +// ProcessOutboundMessages sends messages received from tss out channel to target peers. +// On context cancel stops listening to channel and exits. +func (k *BaseFrostTss) ProcessOutboundMessages(ctx context.Context, outChn chan tss.Message, messageType comm.MessageType) error { + for { + select { + case msg, ok := <-k.Handler.Listen(): + { + if !ok { + k.Done <- true + return nil + } + + msgBytes, err := msg.MarshalBinary() + if err != nil { + return err + } + + peers, err := k.BroadcastPeers(msg) + if err != nil { + return err + } + + k.Log.Debug().Msgf("sending message %s to %s", msg, peers) + + err = k.Communication.Broadcast(peers, msgBytes, messageType, k.SessionID()) + if err != nil { + return err + } + } + case <-ctx.Done(): + { + return nil + } + } + } +} + +func (k *BaseFrostTss) BroadcastPeers(msg *protocol.Message) ([]peer.ID, error) { + if msg.Broadcast { + return k.Peers, nil + } else { + if string(msg.To) == "" { + return []peer.ID{}, nil + } + + p, err := peer.Decode(string(msg.To)) + if err != nil { + return nil, err + } + return []peer.ID{p}, nil + } +} + +func (b *BaseFrostTss) SessionID() string { + return b.SID +} diff --git a/tss/frost/common/util.go b/tss/frost/common/util.go new file mode 100644 index 00000000..135c3438 --- /dev/null +++ b/tss/frost/common/util.go @@ -0,0 +1,22 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package common + +import ( + "sort" + + mapset "github.com/deckarep/golang-set/v2" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/taurusgroup/multi-party-sig/pkg/party" +) + +func PartyIDSFromPeers(peers peer.IDSlice) []party.ID { + sort.Sort(peers) + peerSet := mapset.NewSet[peer.ID](peers...) + idSlice := make([]party.ID, len(peerSet.ToSlice())) + for i, peer := range peerSet.ToSlice() { + idSlice[i] = party.ID(peer.Pretty()) + } + return idSlice +} diff --git a/tss/frost/keygen/keygen.go b/tss/frost/keygen/keygen.go new file mode 100644 index 00000000..75242ff2 --- /dev/null +++ b/tss/frost/keygen/keygen.go @@ -0,0 +1,152 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package keygen + +import ( + "context" + "encoding/hex" + "errors" + + "github.com/ChainSafe/sygma-relayer/comm" + "github.com/ChainSafe/sygma-relayer/keyshare" + "github.com/ChainSafe/sygma-relayer/tss/frost/common" + "github.com/binance-chain/tss-lib/tss" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/rs/zerolog/log" + "github.com/sourcegraph/conc/pool" + "github.com/taurusgroup/multi-party-sig/pkg/party" + "github.com/taurusgroup/multi-party-sig/pkg/protocol" + "github.com/taurusgroup/multi-party-sig/protocols/frost" +) + +type FrostKeyshareStorer interface { + StoreKeyshare(keyshare keyshare.FrostKeyshare) error + LockKeyshare() + UnlockKeyshare() + GetKeyshare() (keyshare.FrostKeyshare, error) +} + +type Keygen struct { + common.BaseFrostTss + storer FrostKeyshareStorer + threshold int + subscriptionID comm.SubscriptionID +} + +func NewKeygen( + sessionID string, + threshold int, + host host.Host, + comm comm.Communication, + storer FrostKeyshareStorer, +) *Keygen { + storer.LockKeyshare() + return &Keygen{ + BaseFrostTss: common.BaseFrostTss{ + Host: host, + Communication: comm, + Peers: host.Peerstore().Peers(), + SID: sessionID, + Log: log.With().Str("SessionID", sessionID).Str("Process", "keygen").Logger(), + Cancel: func() {}, + Done: make(chan bool), + }, + storer: storer, + threshold: threshold, + } +} + +// Run initializes the keygen party and runs the keygen tss process. +// +// Should be run only after all the participating parties are ready. +func (k *Keygen) Run( + ctx context.Context, + coordinator bool, + resultChn chan interface{}, + params []byte, +) error { + ctx, k.Cancel = context.WithCancel(ctx) + defer k.Stop() + + outChn := make(chan tss.Message) + msgChn := make(chan *comm.WrappedMessage) + k.subscriptionID = k.Communication.Subscribe(k.SessionID(), comm.TssKeyGenMsg, msgChn) + + var err error + k.Handler, err = protocol.NewMultiHandler( + frost.KeygenTaproot( + party.ID(k.Host.ID().Pretty()), + common.PartyIDSFromPeers(append(k.Host.Peerstore().Peers(), k.Host.ID())), + k.threshold), + []byte(k.SessionID())) + if err != nil { + return err + } + + p := pool.New().WithContext(ctx).WithCancelOnError() + p.Go(func(ctx context.Context) error { return k.ProcessOutboundMessages(ctx, outChn, comm.TssKeyGenMsg) }) + p.Go(func(ctx context.Context) error { return k.ProcessInboundMessages(ctx, msgChn) }) + p.Go(func(ctx context.Context) error { return k.processEndMessage(ctx) }) + return p.Wait() +} + +// Stop ends all subscriptions created when starting the tss process and unlocks keyshare. +func (k *Keygen) Stop() { + k.Communication.UnSubscribe(k.subscriptionID) + k.storer.UnlockKeyshare() + k.Cancel() +} + +// Ready returns true if all parties from the peerstore are ready. +// Error is returned if excluded peers exist as we need all peers to participate +// in keygen process. +func (k *Keygen) Ready(readyMap map[peer.ID]bool, excludedPeers []peer.ID) (bool, error) { + if len(excludedPeers) > 0 { + return false, errors.New("error") + } + + return len(readyMap) == len(k.Host.Peerstore().Peers()), nil +} + +// ValidCoordinators returns all peers in peerstore +func (k *Keygen) ValidCoordinators() []peer.ID { + return k.Peers +} + +func (k *Keygen) StartParams(readyMap map[peer.ID]bool) []byte { + return []byte{} +} + +func (k *Keygen) Retryable() bool { + return false +} + +// processEndMessage waits for the final message with generated key share and stores it locally. +func (k *Keygen) processEndMessage(ctx context.Context) error { + + for { + select { + case <-k.Done: + { + result, err := k.Handler.Result() + if err != nil { + return err + } + taprootConfig := result.(*frost.TaprootConfig) + + err = k.storer.StoreKeyshare(keyshare.NewFrostKeyshare(taprootConfig, k.threshold, k.Peers)) + if err != nil { + return err + } + + k.Log.Info().Msgf("Generated public key %s", hex.EncodeToString(taprootConfig.PublicKey)) + k.Cancel() + return nil + } + case <-ctx.Done(): + return nil + } + } +} diff --git a/tss/frost/keygen/keygen_test.go b/tss/frost/keygen/keygen_test.go new file mode 100644 index 00000000..6d307630 --- /dev/null +++ b/tss/frost/keygen/keygen_test.go @@ -0,0 +1,57 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package keygen_test + +import ( + "context" + "testing" + + "github.com/ChainSafe/sygma-relayer/comm" + "github.com/ChainSafe/sygma-relayer/comm/elector" + "github.com/ChainSafe/sygma-relayer/tss" + "github.com/ChainSafe/sygma-relayer/tss/frost/keygen" + tsstest "github.com/ChainSafe/sygma-relayer/tss/test" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/sourcegraph/conc/pool" + "github.com/stretchr/testify/suite" + "go.uber.org/mock/gomock" +) + +type KeygenTestSuite struct { + tsstest.CoordinatorTestSuite +} + +func TestRunKeygenTestSuite(t *testing.T) { + suite.Run(t, new(KeygenTestSuite)) +} + +func (s *KeygenTestSuite) Test_ValidKeygenProcess() { + communicationMap := make(map[peer.ID]*tsstest.TestCommunication) + coordinators := []*tss.Coordinator{} + processes := []tss.TssProcess{} + + for _, host := range s.CoordinatorTestSuite.Hosts { + communication := tsstest.TestCommunication{ + Host: host, + Subscriptions: make(map[comm.SubscriptionID]chan *comm.WrappedMessage), + } + communicationMap[host.ID()] = &communication + s.MockFrostStorer.EXPECT().LockKeyshare() + keygen := keygen.NewKeygen("keygen", s.Threshold, host, &communication, s.MockFrostStorer) + electorFactory := elector.NewCoordinatorElectorFactory(host, s.BullyConfig) + coordinators = append(coordinators, tss.NewCoordinator(host, &communication, electorFactory)) + processes = append(processes, keygen) + } + tsstest.SetupCommunication(communicationMap) + s.MockFrostStorer.EXPECT().StoreKeyshare(gomock.Any()).Times(3) + s.MockFrostStorer.EXPECT().UnlockKeyshare().Times(3) + + pool := pool.New().WithContext(context.Background()).WithCancelOnError() + for i, coordinator := range coordinators { + pool.Go(func(ctx context.Context) error { return coordinator.Execute(ctx, processes[i], nil) }) + } + + err := pool.Wait() + s.Nil(err) +} diff --git a/tss/frost/resharing/resharing.go b/tss/frost/resharing/resharing.go new file mode 100644 index 00000000..876f207a --- /dev/null +++ b/tss/frost/resharing/resharing.go @@ -0,0 +1,163 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package resharing + +import ( + "context" + + "github.com/ChainSafe/sygma-relayer/comm" + "github.com/ChainSafe/sygma-relayer/keyshare" + "github.com/ChainSafe/sygma-relayer/tss/frost/common" + "github.com/binance-chain/tss-lib/tss" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/rs/zerolog/log" + "github.com/sourcegraph/conc/pool" + "github.com/taurusgroup/multi-party-sig/pkg/math/curve" + "github.com/taurusgroup/multi-party-sig/pkg/party" + "github.com/taurusgroup/multi-party-sig/pkg/protocol" + "github.com/taurusgroup/multi-party-sig/protocols/frost" +) + +type FrostKeyshareStorer interface { + GetKeyshare() (keyshare.FrostKeyshare, error) + StoreKeyshare(keyshare keyshare.FrostKeyshare) error + LockKeyshare() + UnlockKeyshare() +} + +type Resharing struct { + common.BaseFrostTss + key keyshare.FrostKeyshare + subscriptionID comm.SubscriptionID + storer FrostKeyshareStorer + newThreshold int +} + +func NewResharing( + sessionID string, + threshold int, + host host.Host, + comm comm.Communication, + storer FrostKeyshareStorer, +) *Resharing { + storer.LockKeyshare() + var key keyshare.FrostKeyshare + key, err := storer.GetKeyshare() + if err != nil { + // empty key for parties that don't have one + key = keyshare.FrostKeyshare{ + Key: &frost.TaprootConfig{ + Threshold: threshold, + PublicKey: nil, + PrivateShare: &curve.Secp256k1Scalar{}, + VerificationShares: make(map[party.ID]*curve.Secp256k1Point), + ID: party.ID(host.ID().Pretty()), + }, + } + } + + return &Resharing{ + BaseFrostTss: common.BaseFrostTss{ + Host: host, + Communication: comm, + Peers: host.Peerstore().Peers(), + SID: sessionID, + Log: log.With().Str("SessionID", sessionID).Str("Process", "resharing").Logger(), + Cancel: func() {}, + Done: make(chan bool), + }, + key: key, + storer: storer, + newThreshold: threshold, + } +} + +// Run initializes the signing party and runs the resharing tss process. +// Params contains peer subset that leaders sends with start message. +func (r *Resharing) Run( + ctx context.Context, + coordinator bool, + resultChn chan interface{}, + params []byte, +) error { + ctx, r.Cancel = context.WithCancel(ctx) + var err error + + outChn := make(chan tss.Message) + msgChn := make(chan *comm.WrappedMessage) + r.subscriptionID = r.Communication.Subscribe(r.SessionID(), comm.TssReshareMsg, msgChn) + + r.key.Key.PublicKey = params + r.Handler, err = protocol.NewMultiHandler( + frost.RefreshTaproot( + r.key.Key, + common.PartyIDSFromPeers(append(r.Host.Peerstore().Peers(), r.Host.ID()))), + []byte(r.SessionID())) + if err != nil { + return err + } + + defer r.Stop() + p := pool.New().WithContext(ctx).WithCancelOnError() + p.Go(func(ctx context.Context) error { return r.ProcessOutboundMessages(ctx, outChn, comm.TssReshareMsg) }) + p.Go(func(ctx context.Context) error { return r.ProcessInboundMessages(ctx, msgChn) }) + p.Go(func(ctx context.Context) error { return r.processEndMessage(ctx) }) + + r.Log.Info().Msgf("Started resharing process") + return p.Wait() +} + +// Stop ends all subscriptions created when starting the tss process and unlocks keyshare. +func (r *Resharing) Stop() { + log.Info().Str("sessionID", r.SessionID()).Msgf("Stopping tss process.") + r.Communication.UnSubscribe(r.subscriptionID) + r.storer.UnlockKeyshare() + r.Cancel() +} + +// Ready returns true if all parties from peerstore are ready +func (r *Resharing) Ready(readyMap map[peer.ID]bool, excludedPeers []peer.ID) (bool, error) { + return len(readyMap) == len(r.Host.Peerstore().Peers()), nil +} + +func (r *Resharing) ValidCoordinators() []peer.ID { + return r.key.Peers +} + +func (r *Resharing) StartParams(readyMap map[peer.ID]bool) []byte { + return r.key.Key.PublicKey +} + +func (r *Resharing) Retryable() bool { + return false +} + +// processEndMessage waits for the final message with generated key share and stores it locally. +func (r *Resharing) processEndMessage(ctx context.Context) error { + + for { + select { + case <-r.Done: + { + result, err := r.Handler.Result() + if err != nil { + return err + } + taprootConfig := result.(*frost.TaprootConfig) + + err = r.storer.StoreKeyshare(keyshare.NewFrostKeyshare(taprootConfig, r.newThreshold, r.Peers)) + if err != nil { + return err + } + + r.Log.Info().Msgf("Refreshed key") + r.Cancel() + return nil + } + case <-ctx.Done(): + return nil + } + } +} diff --git a/tss/frost/resharing/resharing_test.go b/tss/frost/resharing/resharing_test.go new file mode 100644 index 00000000..7657db2c --- /dev/null +++ b/tss/frost/resharing/resharing_test.go @@ -0,0 +1,76 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package resharing_test + +import ( + "context" + "fmt" + "testing" + + "github.com/ChainSafe/sygma-relayer/comm" + "github.com/ChainSafe/sygma-relayer/comm/elector" + "github.com/ChainSafe/sygma-relayer/keyshare" + "github.com/ChainSafe/sygma-relayer/tss" + "github.com/ChainSafe/sygma-relayer/tss/frost/resharing" + tsstest "github.com/ChainSafe/sygma-relayer/tss/test" + "github.com/golang/mock/gomock" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/peerstore" + "github.com/sourcegraph/conc/pool" + "github.com/stretchr/testify/suite" +) + +type ResharingTestSuite struct { + tsstest.CoordinatorTestSuite +} + +func TestRunResharingTestSuite(t *testing.T) { + suite.Run(t, new(ResharingTestSuite)) +} + +func (s *ResharingTestSuite) Test_ValidResharingProcess_OldAndNewSubset() { + communicationMap := make(map[peer.ID]*tsstest.TestCommunication) + coordinators := []*tss.Coordinator{} + processes := []tss.TssProcess{} + + hosts := []host.Host{} + for i := 0; i < s.PartyNumber+1; i++ { + host, _ := tsstest.NewHost(i) + hosts = append(hosts, host) + } + for _, host := range hosts { + for _, peer := range hosts { + host.Peerstore().AddAddr(peer.ID(), peer.Addrs()[0], peerstore.PermanentAddrTTL) + } + } + + for i, host := range hosts { + communication := tsstest.TestCommunication{ + Host: host, + Subscriptions: make(map[comm.SubscriptionID]chan *comm.WrappedMessage), + } + communicationMap[host.ID()] = &communication + storer := keyshare.NewFrostKeyshareStore(fmt.Sprintf("../../test/keyshares/%d-frost.keyshare", i)) + share, err := storer.GetKeyshare() + s.MockFrostStorer.EXPECT().LockKeyshare() + s.MockFrostStorer.EXPECT().UnlockKeyshare() + s.MockFrostStorer.EXPECT().GetKeyshare().Return(share, err) + s.MockFrostStorer.EXPECT().StoreKeyshare(gomock.Any()).Return(nil) + resharing := resharing.NewResharing("resharing2", 1, host, &communication, s.MockFrostStorer) + electorFactory := elector.NewCoordinatorElectorFactory(host, s.BullyConfig) + coordinators = append(coordinators, tss.NewCoordinator(host, &communication, electorFactory)) + processes = append(processes, resharing) + } + tsstest.SetupCommunication(communicationMap) + + resultChn := make(chan interface{}) + pool := pool.New().WithContext(context.Background()).WithCancelOnError() + for i, coordinator := range coordinators { + pool.Go(func(ctx context.Context) error { return coordinator.Execute(ctx, processes[i], resultChn) }) + } + + err := pool.Wait() + s.Nil(err) +} diff --git a/tss/frost/signing/signing.go b/tss/frost/signing/signing.go new file mode 100644 index 00000000..fbd2efd9 --- /dev/null +++ b/tss/frost/signing/signing.go @@ -0,0 +1,244 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package signing + +import ( + "context" + "encoding/hex" + "encoding/json" + "math/big" + + errors "github.com/ChainSafe/sygma-relayer/tss" + "github.com/binance-chain/tss-lib/tss" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/rs/zerolog/log" + "github.com/sourcegraph/conc/pool" + "github.com/taurusgroup/multi-party-sig/pkg/math/curve" + "github.com/taurusgroup/multi-party-sig/pkg/protocol" + "github.com/taurusgroup/multi-party-sig/pkg/taproot" + "github.com/taurusgroup/multi-party-sig/protocols/frost" + "golang.org/x/exp/slices" + + "github.com/ChainSafe/sygma-relayer/comm" + "github.com/ChainSafe/sygma-relayer/keyshare" + "github.com/ChainSafe/sygma-relayer/tss/frost/common" + "github.com/ChainSafe/sygma-relayer/tss/util" +) + +type Signature struct { + Id int + Signature taproot.Signature +} + +type SaveDataFetcher interface { + GetKeyshare() (keyshare.FrostKeyshare, error) + LockKeyshare() + UnlockKeyshare() +} + +type Signing struct { + common.BaseFrostTss + id int + coordinator bool + key keyshare.FrostKeyshare + msg *big.Int + resultChn chan interface{} + subscriptionID comm.SubscriptionID +} + +func NewSigning( + id int, + msg *big.Int, + tweak string, + sessionID string, + host host.Host, + comm comm.Communication, + fetcher SaveDataFetcher, +) (*Signing, error) { + fetcher.LockKeyshare() + defer fetcher.UnlockKeyshare() + key, err := fetcher.GetKeyshare() + if err != nil { + return nil, err + } + + tweakBytes, err := hex.DecodeString(tweak) + if err != nil { + return nil, err + } + + h := &curve.Secp256k1Scalar{} + err = h.UnmarshalBinary(tweakBytes) + if err != nil { + return nil, err + } + key.Key, err = key.Key.Derive(h, nil) + if err != nil { + return nil, err + } + + return &Signing{ + BaseFrostTss: common.BaseFrostTss{ + Host: host, + Communication: comm, + Peers: key.Peers, + SID: sessionID, + Log: log.With().Str("SessionID", sessionID).Str("Process", "signing").Logger(), + Cancel: func() {}, + Done: make(chan bool), + }, + key: key, + id: id, + msg: msg, + }, nil +} + +// Run initializes the signing party and runs the signing tss process. +// Params contains peer subset that leaders sends with start message. +func (s *Signing) Run( + ctx context.Context, + coordinator bool, + resultChn chan interface{}, + params []byte, +) error { + s.coordinator = coordinator + s.resultChn = resultChn + ctx, s.Cancel = context.WithCancel(ctx) + + peerSubset, err := s.unmarshallStartParams(params) + if err != nil { + return err + } + s.Peers = peerSubset + if !util.IsParticipant(s.Host.ID(), peerSubset) { + return &errors.SubsetError{Peer: s.Host.ID()} + } + + msgChn := make(chan *comm.WrappedMessage) + s.subscriptionID = s.Communication.Subscribe(s.SessionID(), comm.TssKeySignMsg, msgChn) + defer s.Stop() + s.Handler, err = protocol.NewMultiHandler( + frost.SignTaproot( + s.key.Key, + common.PartyIDSFromPeers(peerSubset), + s.msg.Bytes(), + ), + []byte(s.SessionID())) + if err != nil { + return err + } + + outChn := make(chan tss.Message) + p := pool.New().WithContext(ctx).WithCancelOnError() + p.Go(func(ctx context.Context) error { return s.ProcessOutboundMessages(ctx, outChn, comm.TssKeySignMsg) }) + p.Go(func(ctx context.Context) error { return s.ProcessInboundMessages(ctx, msgChn) }) + p.Go(func(ctx context.Context) error { return s.processEndMessage(ctx) }) + + s.Log.Info().Msgf("Started signing process") + return p.Wait() +} + +// Stop ends all subscriptions created when starting the tss process. +func (s *Signing) Stop() { + log.Info().Str("sessionID", s.SessionID()).Msgf("Stopping tss process.") + s.Communication.UnSubscribe(s.subscriptionID) + s.Cancel() +} + +// Ready returns true if threshold+1 parties are ready to start the signing process. +func (s *Signing) Ready(readyMap map[peer.ID]bool, excludedPeers []peer.ID) (bool, error) { + readyMap = s.readyParticipants(readyMap) + return len(readyMap) == s.key.Threshold+1, nil +} + +// ValidCoordinators returns only peers that have a valid keyshare +func (s *Signing) ValidCoordinators() []peer.ID { + return s.key.Peers +} + +// StartParams returns peer subset for this tss process. It is calculated +// by sorting hashes of peer IDs and session ID and chosing ready peers alphabetically +// until threshold is satisfied. +func (s *Signing) StartParams(readyMap map[peer.ID]bool) []byte { + readyMap = s.readyParticipants(readyMap) + peers := []peer.ID{} + for peer := range readyMap { + peers = append(peers, peer) + } + + sortedPeers := util.SortPeersForSession(peers, s.SessionID()) + peerSubset := []peer.ID{} + for _, peer := range sortedPeers { + peerSubset = append(peerSubset, peer.ID) + if len(peerSubset) == s.key.Threshold+1 { + break + } + } + + paramBytes, _ := json.Marshal(peerSubset) + return paramBytes +} + +func (s *Signing) unmarshallStartParams(paramBytes []byte) ([]peer.ID, error) { + var peerSubset []peer.ID + err := json.Unmarshal(paramBytes, &peerSubset) + if err != nil { + return []peer.ID{}, err + } + + return peerSubset, nil +} + +// processEndMessage routes signature to result channel. +func (s *Signing) processEndMessage(ctx context.Context) error { + defer s.Cancel() + for { + select { + case <-s.Done: + { + s.Log.Info().Msg("Successfully generated signature") + + result, err := s.Handler.Result() + if err != nil { + return err + } + signature, _ := result.(taproot.Signature) + + s.resultChn <- Signature{ + Signature: signature, + Id: s.id, + } + s.Cancel() + return nil + } + case <-ctx.Done(): + { + return nil + } + } + } +} + +// readyParticipants returns all ready peers that contain a valid key share +func (s *Signing) readyParticipants(readyMap map[peer.ID]bool) map[peer.ID]bool { + readyParticipants := make(map[peer.ID]bool) + for peer, ready := range readyMap { + if !ready { + continue + } + + if !slices.Contains(s.key.Peers, peer) { + continue + } + + readyParticipants[peer] = true + } + + return readyParticipants +} + +func (s *Signing) Retryable() bool { + return true +} diff --git a/tss/frost/signing/signing_test.go b/tss/frost/signing/signing_test.go new file mode 100644 index 00000000..b7feb435 --- /dev/null +++ b/tss/frost/signing/signing_test.go @@ -0,0 +1,144 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package signing_test + +import ( + "context" + "encoding/hex" + "fmt" + "math/big" + "testing" + "time" + + "github.com/ChainSafe/sygma-relayer/comm" + "github.com/ChainSafe/sygma-relayer/comm/elector" + "github.com/ChainSafe/sygma-relayer/keyshare" + "github.com/ChainSafe/sygma-relayer/tss" + "github.com/ChainSafe/sygma-relayer/tss/frost/signing" + tsstest "github.com/ChainSafe/sygma-relayer/tss/test" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/sourcegraph/conc/pool" + "github.com/stretchr/testify/suite" + "github.com/taurusgroup/multi-party-sig/pkg/math/curve" +) + +type SigningTestSuite struct { + tsstest.CoordinatorTestSuite +} + +func TestRunSigningTestSuite(t *testing.T) { + suite.Run(t, new(SigningTestSuite)) +} + +func (s *SigningTestSuite) Test_ValidSigningProcess() { + communicationMap := make(map[peer.ID]*tsstest.TestCommunication) + coordinators := []*tss.Coordinator{} + processes := []tss.TssProcess{} + + tweak := "c82aa6ae534bb28aaafeb3660c31d6a52e187d8f05d48bb6bdb9b733a9b42212" + tweakBytes, err := hex.DecodeString(tweak) + s.Nil(err) + h := &curve.Secp256k1Scalar{} + err = h.UnmarshalBinary(tweakBytes) + s.Nil(err) + + fetcher := keyshare.NewFrostKeyshareStore(fmt.Sprintf("../../test/keyshares/%d-frost.keyshare", 0)) + testKeyshare, err := fetcher.GetKeyshare() + s.Nil(err) + tweakedKeyshare, err := testKeyshare.Key.Derive(h, nil) + s.Nil(err) + + msgBytes := []byte("Message") + msg := big.NewInt(0) + msg.SetBytes(msgBytes) + for i, host := range s.Hosts { + communication := tsstest.TestCommunication{ + Host: host, + Subscriptions: make(map[comm.SubscriptionID]chan *comm.WrappedMessage), + } + communicationMap[host.ID()] = &communication + fetcher := keyshare.NewFrostKeyshareStore(fmt.Sprintf("../../test/keyshares/%d-frost.keyshare", i)) + + signing, err := signing.NewSigning(1, msg, tweak, "signing1", host, &communication, fetcher) + if err != nil { + panic(err) + } + electorFactory := elector.NewCoordinatorElectorFactory(host, s.BullyConfig) + coordinators = append(coordinators, tss.NewCoordinator(host, &communication, electorFactory)) + processes = append(processes, signing) + } + tsstest.SetupCommunication(communicationMap) + + resultChn := make(chan interface{}) + + ctx, cancel := context.WithCancel(context.Background()) + pool := pool.New().WithContext(ctx) + for i, coordinator := range coordinators { + coordinator := coordinator + pool.Go(func(ctx context.Context) error { + return coordinator.Execute(ctx, processes[i], resultChn) + }) + } + + sig1 := <-resultChn + sig2 := <-resultChn + tSig1 := sig1.(signing.Signature) + tSig2 := sig2.(signing.Signature) + s.Equal(tweakedKeyshare.PublicKey.Verify(tSig1.Signature, msg.Bytes()), true) + s.Equal(tweakedKeyshare.PublicKey.Verify(tSig2.Signature, msg.Bytes()), true) + cancel() + err = pool.Wait() + s.Nil(err) +} + +func (s *SigningTestSuite) Test_ProcessTimeout() { + communicationMap := make(map[peer.ID]*tsstest.TestCommunication) + coordinators := []*tss.Coordinator{} + processes := []tss.TssProcess{} + + tweak := "c82aa6ae534bb28aaafeb3660c31d6a52e187d8f05d48bb6bdb9b733a9b42212" + tweakBytes, err := hex.DecodeString(tweak) + s.Nil(err) + h := &curve.Secp256k1Scalar{} + err = h.UnmarshalBinary(tweakBytes) + s.Nil(err) + + msgBytes := []byte("Message") + msg := big.NewInt(0) + msg.SetBytes(msgBytes) + for i, host := range s.Hosts { + communication := tsstest.TestCommunication{ + Host: host, + Subscriptions: make(map[comm.SubscriptionID]chan *comm.WrappedMessage), + } + communicationMap[host.ID()] = &communication + fetcher := keyshare.NewFrostKeyshareStore(fmt.Sprintf("../../test/keyshares/%d-frost.keyshare", i)) + + signing, err := signing.NewSigning(1, msg, tweak, "signing1", host, &communication, fetcher) + if err != nil { + panic(err) + } + electorFactory := elector.NewCoordinatorElectorFactory(host, s.BullyConfig) + coordinator := tss.NewCoordinator(host, &communication, electorFactory) + coordinator.TssTimeout = time.Nanosecond + coordinators = append(coordinators, coordinator) + processes = append(processes, signing) + } + tsstest.SetupCommunication(communicationMap) + + resultChn := make(chan interface{}) + + ctx, cancel := context.WithCancel(context.Background()) + pool := pool.New().WithContext(ctx) + for i, coordinator := range coordinators { + coordinator := coordinator + pool.Go(func(ctx context.Context) error { + return coordinator.Execute(ctx, processes[i], resultChn) + }) + } + + err = pool.Wait() + s.NotNil(err) + cancel() +} diff --git a/tss/keygen/mock/storer.go b/tss/keygen/mock/storer.go deleted file mode 100644 index 29a33c90..00000000 --- a/tss/keygen/mock/storer.go +++ /dev/null @@ -1,88 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: ./tss/keygen/keygen.go - -// Package mock_keygen is a generated GoMock package. -package mock_keygen - -import ( - reflect "reflect" - - keyshare "github.com/ChainSafe/sygma-relayer/keyshare" - gomock "github.com/golang/mock/gomock" -) - -// MockSaveDataStorer is a mock of SaveDataStorer interface. -type MockSaveDataStorer struct { - ctrl *gomock.Controller - recorder *MockSaveDataStorerMockRecorder -} - -// MockSaveDataStorerMockRecorder is the mock recorder for MockSaveDataStorer. -type MockSaveDataStorerMockRecorder struct { - mock *MockSaveDataStorer -} - -// NewMockSaveDataStorer creates a new mock instance. -func NewMockSaveDataStorer(ctrl *gomock.Controller) *MockSaveDataStorer { - mock := &MockSaveDataStorer{ctrl: ctrl} - mock.recorder = &MockSaveDataStorerMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockSaveDataStorer) EXPECT() *MockSaveDataStorerMockRecorder { - return m.recorder -} - -// GetKeyshare mocks base method. -func (m *MockSaveDataStorer) GetKeyshare() (keyshare.Keyshare, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetKeyshare") - ret0, _ := ret[0].(keyshare.Keyshare) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetKeyshare indicates an expected call of GetKeyshare. -func (mr *MockSaveDataStorerMockRecorder) GetKeyshare() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetKeyshare", reflect.TypeOf((*MockSaveDataStorer)(nil).GetKeyshare)) -} - -// LockKeyshare mocks base method. -func (m *MockSaveDataStorer) LockKeyshare() { - m.ctrl.T.Helper() - m.ctrl.Call(m, "LockKeyshare") -} - -// LockKeyshare indicates an expected call of LockKeyshare. -func (mr *MockSaveDataStorerMockRecorder) LockKeyshare() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LockKeyshare", reflect.TypeOf((*MockSaveDataStorer)(nil).LockKeyshare)) -} - -// StoreKeyshare mocks base method. -func (m *MockSaveDataStorer) StoreKeyshare(keyshare keyshare.Keyshare) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "StoreKeyshare", keyshare) - ret0, _ := ret[0].(error) - return ret0 -} - -// StoreKeyshare indicates an expected call of StoreKeyshare. -func (mr *MockSaveDataStorerMockRecorder) StoreKeyshare(keyshare interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StoreKeyshare", reflect.TypeOf((*MockSaveDataStorer)(nil).StoreKeyshare), keyshare) -} - -// UnlockKeyshare mocks base method. -func (m *MockSaveDataStorer) UnlockKeyshare() { - m.ctrl.T.Helper() - m.ctrl.Call(m, "UnlockKeyshare") -} - -// UnlockKeyshare indicates an expected call of UnlockKeyshare. -func (mr *MockSaveDataStorerMockRecorder) UnlockKeyshare() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnlockKeyshare", reflect.TypeOf((*MockSaveDataStorer)(nil).UnlockKeyshare)) -} diff --git a/tss/common/messages.go b/tss/message/message.go similarity index 98% rename from tss/common/messages.go rename to tss/message/message.go index 8523fab4..8165493a 100644 --- a/tss/common/messages.go +++ b/tss/message/message.go @@ -1,7 +1,7 @@ // The Licensed Work is (c) 2022 Sygma // SPDX-License-Identifier: LGPL-3.0-only -package common +package message import ( "encoding/json" diff --git a/tss/common/messages_test.go b/tss/message/message_test.go similarity index 64% rename from tss/common/messages_test.go rename to tss/message/message_test.go index 02746b28..e164444a 100644 --- a/tss/common/messages_test.go +++ b/tss/message/message_test.go @@ -1,12 +1,12 @@ // The Licensed Work is (c) 2022 Sygma // SPDX-License-Identifier: LGPL-3.0-only -package common_test +package message_test import ( "testing" - "github.com/ChainSafe/sygma-relayer/tss/common" + "github.com/ChainSafe/sygma-relayer/tss/message" "github.com/stretchr/testify/suite" ) @@ -19,14 +19,14 @@ func TestRunTssMessageTestSuite(t *testing.T) { } func (s *TssMessageTestSuite) Test_UnmarshaledMessageShouldBeEqual() { - originalMsg := &common.TssMessage{ + originalMsg := &message.TssMessage{ MsgBytes: []byte{1}, IsBroadcast: true, } - msgBytes, err := common.MarshalTssMessage(originalMsg.MsgBytes, originalMsg.IsBroadcast) + msgBytes, err := message.MarshalTssMessage(originalMsg.MsgBytes, originalMsg.IsBroadcast) s.Nil(err) - unmarshaledMsg, err := common.UnmarshalTssMessage(msgBytes) + unmarshaledMsg, err := message.UnmarshalTssMessage(msgBytes) s.Nil(err) s.Equal(originalMsg, unmarshaledMsg) @@ -41,13 +41,13 @@ func TestRunStartMessageTestSuite(t *testing.T) { } func (s *StartMessageTestSuite) Test_UnmarshaledMessageShouldBeEqual() { - originalMsg := &common.StartMessage{ + originalMsg := &message.StartMessage{ Params: []byte("test"), } - msgBytes, err := common.MarshalStartMessage(originalMsg.Params) + msgBytes, err := message.MarshalStartMessage(originalMsg.Params) s.Nil(err) - unmarshaledMsg, err := common.UnmarshalStartMessage(msgBytes) + unmarshaledMsg, err := message.UnmarshalStartMessage(msgBytes) s.Nil(err) s.Equal(originalMsg, unmarshaledMsg) diff --git a/tss/mock/ecdsa.go b/tss/mock/ecdsa.go new file mode 100644 index 00000000..7e33d1ca --- /dev/null +++ b/tss/mock/ecdsa.go @@ -0,0 +1,88 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./tss/ecdsa/keygen/keygen.go + +// Package mock_tss is a generated GoMock package. +package mock_tss + +import ( + reflect "reflect" + + keyshare "github.com/ChainSafe/sygma-relayer/keyshare" + gomock "github.com/golang/mock/gomock" +) + +// MockECDSAKeyshareStorer is a mock of ECDSAKeyshareStorer interface. +type MockECDSAKeyshareStorer struct { + ctrl *gomock.Controller + recorder *MockECDSAKeyshareStorerMockRecorder +} + +// MockECDSAKeyshareStorerMockRecorder is the mock recorder for MockECDSAKeyshareStorer. +type MockECDSAKeyshareStorerMockRecorder struct { + mock *MockECDSAKeyshareStorer +} + +// NewMockECDSAKeyshareStorer creates a new mock instance. +func NewMockECDSAKeyshareStorer(ctrl *gomock.Controller) *MockECDSAKeyshareStorer { + mock := &MockECDSAKeyshareStorer{ctrl: ctrl} + mock.recorder = &MockECDSAKeyshareStorerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockECDSAKeyshareStorer) EXPECT() *MockECDSAKeyshareStorerMockRecorder { + return m.recorder +} + +// GetKeyshare mocks base method. +func (m *MockECDSAKeyshareStorer) GetKeyshare() (keyshare.ECDSAKeyshare, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetKeyshare") + ret0, _ := ret[0].(keyshare.ECDSAKeyshare) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetKeyshare indicates an expected call of GetKeyshare. +func (mr *MockECDSAKeyshareStorerMockRecorder) GetKeyshare() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetKeyshare", reflect.TypeOf((*MockECDSAKeyshareStorer)(nil).GetKeyshare)) +} + +// LockKeyshare mocks base method. +func (m *MockECDSAKeyshareStorer) LockKeyshare() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "LockKeyshare") +} + +// LockKeyshare indicates an expected call of LockKeyshare. +func (mr *MockECDSAKeyshareStorerMockRecorder) LockKeyshare() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LockKeyshare", reflect.TypeOf((*MockECDSAKeyshareStorer)(nil).LockKeyshare)) +} + +// StoreKeyshare mocks base method. +func (m *MockECDSAKeyshareStorer) StoreKeyshare(keyshare keyshare.ECDSAKeyshare) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StoreKeyshare", keyshare) + ret0, _ := ret[0].(error) + return ret0 +} + +// StoreKeyshare indicates an expected call of StoreKeyshare. +func (mr *MockECDSAKeyshareStorerMockRecorder) StoreKeyshare(keyshare interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StoreKeyshare", reflect.TypeOf((*MockECDSAKeyshareStorer)(nil).StoreKeyshare), keyshare) +} + +// UnlockKeyshare mocks base method. +func (m *MockECDSAKeyshareStorer) UnlockKeyshare() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "UnlockKeyshare") +} + +// UnlockKeyshare indicates an expected call of UnlockKeyshare. +func (mr *MockECDSAKeyshareStorerMockRecorder) UnlockKeyshare() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnlockKeyshare", reflect.TypeOf((*MockECDSAKeyshareStorer)(nil).UnlockKeyshare)) +} diff --git a/tss/mock/frost.go b/tss/mock/frost.go new file mode 100644 index 00000000..db796900 --- /dev/null +++ b/tss/mock/frost.go @@ -0,0 +1,88 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./tss/frost/keygen/keygen.go + +// Package mock_tss is a generated GoMock package. +package mock_tss + +import ( + reflect "reflect" + + keyshare "github.com/ChainSafe/sygma-relayer/keyshare" + gomock "github.com/golang/mock/gomock" +) + +// MockFrostKeyshareStorer is a mock of FrostKeyshareStorer interface. +type MockFrostKeyshareStorer struct { + ctrl *gomock.Controller + recorder *MockFrostKeyshareStorerMockRecorder +} + +// MockFrostKeyshareStorerMockRecorder is the mock recorder for MockFrostKeyshareStorer. +type MockFrostKeyshareStorerMockRecorder struct { + mock *MockFrostKeyshareStorer +} + +// NewMockFrostKeyshareStorer creates a new mock instance. +func NewMockFrostKeyshareStorer(ctrl *gomock.Controller) *MockFrostKeyshareStorer { + mock := &MockFrostKeyshareStorer{ctrl: ctrl} + mock.recorder = &MockFrostKeyshareStorerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockFrostKeyshareStorer) EXPECT() *MockFrostKeyshareStorerMockRecorder { + return m.recorder +} + +// GetKeyshare mocks base method. +func (m *MockFrostKeyshareStorer) GetKeyshare() (keyshare.FrostKeyshare, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetKeyshare") + ret0, _ := ret[0].(keyshare.FrostKeyshare) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetKeyshare indicates an expected call of GetKeyshare. +func (mr *MockFrostKeyshareStorerMockRecorder) GetKeyshare() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetKeyshare", reflect.TypeOf((*MockFrostKeyshareStorer)(nil).GetKeyshare)) +} + +// LockKeyshare mocks base method. +func (m *MockFrostKeyshareStorer) LockKeyshare() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "LockKeyshare") +} + +// LockKeyshare indicates an expected call of LockKeyshare. +func (mr *MockFrostKeyshareStorerMockRecorder) LockKeyshare() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LockKeyshare", reflect.TypeOf((*MockFrostKeyshareStorer)(nil).LockKeyshare)) +} + +// StoreKeyshare mocks base method. +func (m *MockFrostKeyshareStorer) StoreKeyshare(keyshare keyshare.FrostKeyshare) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StoreKeyshare", keyshare) + ret0, _ := ret[0].(error) + return ret0 +} + +// StoreKeyshare indicates an expected call of StoreKeyshare. +func (mr *MockFrostKeyshareStorerMockRecorder) StoreKeyshare(keyshare interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StoreKeyshare", reflect.TypeOf((*MockFrostKeyshareStorer)(nil).StoreKeyshare), keyshare) +} + +// UnlockKeyshare mocks base method. +func (m *MockFrostKeyshareStorer) UnlockKeyshare() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "UnlockKeyshare") +} + +// UnlockKeyshare indicates an expected call of UnlockKeyshare. +func (mr *MockFrostKeyshareStorerMockRecorder) UnlockKeyshare() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnlockKeyshare", reflect.TypeOf((*MockFrostKeyshareStorer)(nil).UnlockKeyshare)) +} diff --git a/tss/mock/storer.go b/tss/mock/storer.go index d8e73219..5a6a5667 100644 --- a/tss/mock/storer.go +++ b/tss/mock/storer.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: ./tss/resharing/resharing.go +// Source: ./tss/ecdsa/resharing/resharing.go // Package mock_tss is a generated GoMock package. package mock_tss @@ -35,10 +35,10 @@ func (m *MockSaveDataStorer) EXPECT() *MockSaveDataStorerMockRecorder { } // GetKeyshare mocks base method. -func (m *MockSaveDataStorer) GetKeyshare() (keyshare.Keyshare, error) { +func (m *MockSaveDataStorer) GetKeyshare() (keyshare.ECDSAKeyshare, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetKeyshare") - ret0, _ := ret[0].(keyshare.Keyshare) + ret0, _ := ret[0].(keyshare.ECDSAKeyshare) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -62,7 +62,7 @@ func (mr *MockSaveDataStorerMockRecorder) LockKeyshare() *gomock.Call { } // StoreKeyshare mocks base method. -func (m *MockSaveDataStorer) StoreKeyshare(keyshare keyshare.Keyshare) error { +func (m *MockSaveDataStorer) StoreKeyshare(keyshare keyshare.ECDSAKeyshare) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "StoreKeyshare", keyshare) ret0, _ := ret[0].(error) diff --git a/tss/test/communication.go b/tss/test/communication.go index 73aa8436..02409048 100644 --- a/tss/test/communication.go +++ b/tss/test/communication.go @@ -69,6 +69,7 @@ func (ts *TestCommunication) UnSubscribe(subscriptionID comm.SubscriptionID) { } func (ts *TestCommunication) ReceiveMessage(msg *comm.WrappedMessage, topic comm.MessageType, sessionID string) { + // simulate real world conditions ts.Subscriptions[comm.SubscriptionID(fmt.Sprintf("%s-%s", sessionID, topic))] <- msg } diff --git a/tss/test/keyshares/0-frost.keyshare b/tss/test/keyshares/0-frost.keyshare new file mode 100755 index 00000000..2b3cf4d8 --- /dev/null +++ b/tss/test/keyshares/0-frost.keyshare @@ -0,0 +1 @@ +{"Key":{"ID":"QmcvEg7jGvuxdsUFRUiE4VdrL2P1Yeju5L83BsJvvXz7zX","Threshold":1,"PrivateShare":"hpUx9M/dN7lAF20Jum3/4sgmfty5W4VNeGoEEB18870=","PublicKey":"Y/S0qwux1qNkvegQrq2N1XHOoYWG4qhNamQOb/gMtCE=","ChainKey":null,"VerificationShares":{"QmYAYuLUPNwYEBYJaKHcE7NKjUhiUV8txx2xDXHvcYa1xK":"Au1e9fpMRj99yTydAjKGGa9H7m/jEpEvySUmTu1xYR9h","QmcvEg7jGvuxdsUFRUiE4VdrL2P1Yeju5L83BsJvvXz7zX":"A2d8jl8IhIujFV0U8bALdmmV0ZWUz4AzZZ5ULyfnGGj5","QmeTuMtdpPB7zKDgmobEwSvxodrf5aFVSmBXX3SQJVjJaT":"A0q++BBQom8GWRs10PzHvBe7ACqQXlIJ5GXIK1GnQ1md"}},"Threshold":1,"Peers":["QmcvEg7jGvuxdsUFRUiE4VdrL2P1Yeju5L83BsJvvXz7zX","QmeTuMtdpPB7zKDgmobEwSvxodrf5aFVSmBXX3SQJVjJaT","QmYAYuLUPNwYEBYJaKHcE7NKjUhiUV8txx2xDXHvcYa1xK"]} \ No newline at end of file diff --git a/tss/test/keyshares/1-frost.keyshare b/tss/test/keyshares/1-frost.keyshare new file mode 100755 index 00000000..37c4ab9c --- /dev/null +++ b/tss/test/keyshares/1-frost.keyshare @@ -0,0 +1 @@ +{"Key":{"ID":"QmeTuMtdpPB7zKDgmobEwSvxodrf5aFVSmBXX3SQJVjJaT","Threshold":1,"PrivateShare":"GQPMThf632EXeXn79BT5KYIg8E3F5DBBzYE+zMl5Sx8=","PublicKey":"Y/S0qwux1qNkvegQrq2N1XHOoYWG4qhNamQOb/gMtCE=","ChainKey":null,"VerificationShares":{"QmYAYuLUPNwYEBYJaKHcE7NKjUhiUV8txx2xDXHvcYa1xK":"Au1e9fpMRj99yTydAjKGGa9H7m/jEpEvySUmTu1xYR9h","QmcvEg7jGvuxdsUFRUiE4VdrL2P1Yeju5L83BsJvvXz7zX":"A2d8jl8IhIujFV0U8bALdmmV0ZWUz4AzZZ5ULyfnGGj5","QmeTuMtdpPB7zKDgmobEwSvxodrf5aFVSmBXX3SQJVjJaT":"A0q++BBQom8GWRs10PzHvBe7ACqQXlIJ5GXIK1GnQ1md"}},"Threshold":1,"Peers":["QmeTuMtdpPB7zKDgmobEwSvxodrf5aFVSmBXX3SQJVjJaT","QmcvEg7jGvuxdsUFRUiE4VdrL2P1Yeju5L83BsJvvXz7zX","QmYAYuLUPNwYEBYJaKHcE7NKjUhiUV8txx2xDXHvcYa1xK"]} \ No newline at end of file diff --git a/tss/test/keyshares/2-frost.keyshare b/tss/test/keyshares/2-frost.keyshare new file mode 100755 index 00000000..36736d64 --- /dev/null +++ b/tss/test/keyshares/2-frost.keyshare @@ -0,0 +1 @@ +{"Key":{"ID":"QmYAYuLUPNwYEBYJaKHcE7NKjUhiUV8txx2xDXHvcYa1xK","Threshold":1,"PrivateShare":"X6lXSE+3v/6oYjsx5Yxu1mw7ouC928mpftYtZ+zOLKM=","PublicKey":"Y/S0qwux1qNkvegQrq2N1XHOoYWG4qhNamQOb/gMtCE=","ChainKey":null,"VerificationShares":{"QmYAYuLUPNwYEBYJaKHcE7NKjUhiUV8txx2xDXHvcYa1xK":"Au1e9fpMRj99yTydAjKGGa9H7m/jEpEvySUmTu1xYR9h","QmcvEg7jGvuxdsUFRUiE4VdrL2P1Yeju5L83BsJvvXz7zX":"A2d8jl8IhIujFV0U8bALdmmV0ZWUz4AzZZ5ULyfnGGj5","QmeTuMtdpPB7zKDgmobEwSvxodrf5aFVSmBXX3SQJVjJaT":"A0q++BBQom8GWRs10PzHvBe7ACqQXlIJ5GXIK1GnQ1md"}},"Threshold":1,"Peers":["QmeTuMtdpPB7zKDgmobEwSvxodrf5aFVSmBXX3SQJVjJaT","QmcvEg7jGvuxdsUFRUiE4VdrL2P1Yeju5L83BsJvvXz7zX","QmYAYuLUPNwYEBYJaKHcE7NKjUhiUV8txx2xDXHvcYa1xK"]} \ No newline at end of file diff --git a/tss/test/utils.go b/tss/test/utils.go index ff188ed5..2d464537 100644 --- a/tss/test/utils.go +++ b/tss/test/utils.go @@ -24,7 +24,8 @@ import ( type CoordinatorTestSuite struct { suite.Suite GomockController *gomock.Controller - MockStorer *mock_tss.MockSaveDataStorer + MockECDSAStorer *mock_tss.MockECDSAKeyshareStorer + MockFrostStorer *mock_tss.MockFrostKeyshareStorer MockCommunication *mock_comm.MockCommunication MockTssProcess *mock_tss.MockTssProcess @@ -36,7 +37,8 @@ type CoordinatorTestSuite struct { func (s *CoordinatorTestSuite) SetupTest() { s.GomockController = gomock.NewController(s.T()) - s.MockStorer = mock_tss.NewMockSaveDataStorer(s.GomockController) + s.MockECDSAStorer = mock_tss.NewMockECDSAKeyshareStorer(s.GomockController) + s.MockFrostStorer = mock_tss.NewMockFrostKeyshareStorer(s.GomockController) s.MockCommunication = mock_comm.NewMockCommunication(s.GomockController) s.MockTssProcess = mock_tss.NewMockTssProcess(s.GomockController) s.PartyNumber = 3 @@ -63,7 +65,7 @@ func (s *CoordinatorTestSuite) SetupTest() { } func NewHost(i int) (host.Host, error) { - privBytes, err := os.ReadFile(fmt.Sprintf("../test/pks/%d.pk", i)) + privBytes, err := os.ReadFile(fmt.Sprintf("../../test/pks/%d.pk", i)) if err != nil { return nil, err } diff --git a/tss/common/sort.go b/tss/util/sort.go similarity index 98% rename from tss/common/sort.go rename to tss/util/sort.go index 2ed03b92..093151ed 100644 --- a/tss/common/sort.go +++ b/tss/util/sort.go @@ -1,7 +1,7 @@ // The Licensed Work is (c) 2022 Sygma // SPDX-License-Identifier: LGPL-3.0-only -package common +package util import ( "encoding/binary" diff --git a/tss/util/util.go b/tss/util/util.go new file mode 100644 index 00000000..3da08c61 --- /dev/null +++ b/tss/util/util.go @@ -0,0 +1,33 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package util + +import ( + "sort" + + "github.com/libp2p/go-libp2p/core/peer" +) + +func SortPeersForSession(peers []peer.ID, sessionID string) SortablePeerSlice { + sortedPeers := make(SortablePeerSlice, len(peers)) + for i, p := range peers { + pMsg := PeerMsg{ + ID: p, + SessionID: sessionID, + } + sortedPeers[i] = pMsg + } + sort.Sort(sortedPeers) + return sortedPeers +} + +func IsParticipant(peer peer.ID, peers peer.IDSlice) bool { + for _, p := range peers { + if p.Pretty() == peer.Pretty() { + return true + } + } + + return false +} diff --git a/tss/util/util_test.go b/tss/util/util_test.go new file mode 100644 index 00000000..2a029e44 --- /dev/null +++ b/tss/util/util_test.go @@ -0,0 +1,69 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package util_test + +import ( + "testing" + + "github.com/ChainSafe/sygma-relayer/tss/util" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/suite" +) + +type IsParticipantTestSuite struct { + suite.Suite +} + +func TestRunIsParticipantTestSuite(t *testing.T) { + suite.Run(t, new(IsParticipantTestSuite)) +} + +func (s *IsParticipantTestSuite) Test_ValidParticipant() { + peerID1 := "QmZHPnN3CKiTAp8VaJqszbf8m7v4mPh15M421KpVdYHF54" + peerID2 := "QmZHPnN3CKiTAp8VaJqszbf8m7v4mPh15M421KpVdYHF56" + peers := peer.IDSlice{peer.ID(peerID1), peer.ID(peerID2)} + + isParticipant := util.IsParticipant(peer.ID(peerID1), peers) + + s.Equal(true, isParticipant) +} + +func (s *IsParticipantTestSuite) Test_InvalidParticipant() { + peerID1 := "QmZHPnN3CKiTAp8VaJqszbf8m7v4mPh15M421KpVdYHF54" + peerID2 := "QmZHPnN3CKiTAp8VaJqszbf8m7v4mPh15M421KpVdYHF56" + peers := peer.IDSlice{peer.ID(peerID2)} + + isParticipant := util.IsParticipant(peer.ID(peerID1), peers) + + s.Equal(false, isParticipant) +} + +type SortPeersForSessionTestSuite struct { + suite.Suite +} + +func TestRunSortPeersForSessionTestSuite(t *testing.T) { + suite.Run(t, new(SortPeersForSessionTestSuite)) +} + +func (s *SortPeersForSessionTestSuite) Test_NoPeers() { + sortedPeers := util.SortPeersForSession([]peer.ID{}, "sessioniD") + + s.Equal(sortedPeers, util.SortablePeerSlice{}) +} + +func (s *SortPeersForSessionTestSuite) Test_ValidPeers() { + peer1, _ := peer.Decode("QmcW3oMdSqoEcjbyd51auqC23vhKX6BqfcZcY2HJ3sKAZR") + peer2, _ := peer.Decode("QmZHPnN3CKiTAp8VaJqszbf8m7v4mPh15M421KpVdYHF54") + peer3, _ := peer.Decode("QmYayosTHxL2xa4jyrQ2PmbhGbrkSxsGM1kzXLTT8SsLVy") + peers := []peer.ID{peer3, peer2, peer1} + + sortedPeers := util.SortPeersForSession(peers, "sessionID") + + s.Equal(sortedPeers, util.SortablePeerSlice{ + util.PeerMsg{SessionID: "sessionID", ID: peer1}, + util.PeerMsg{SessionID: "sessionID", ID: peer2}, + util.PeerMsg{SessionID: "sessionID", ID: peer3}, + }) +}