From 7bb24be084a65958261b61aa04c49f1f70d309e4 Mon Sep 17 00:00:00 2001 From: Gregory Markou Date: Wed, 19 Aug 2020 21:02:21 +0300 Subject: [PATCH 01/21] add metrics --- cmd/chainbridge/main.go | 3 +++ core/core.go | 9 ++++++++ metrics/http.go | 51 +++++++++++++++++++++++++++++++++++++++++ metrics/metrics.go | 18 +++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 metrics/http.go create mode 100644 metrics/metrics.go diff --git a/cmd/chainbridge/main.go b/cmd/chainbridge/main.go index b9efa7efe..f5cc1e190 100644 --- a/cmd/chainbridge/main.go +++ b/cmd/chainbridge/main.go @@ -17,6 +17,7 @@ import ( "github.com/ChainSafe/ChainBridge/config" "github.com/ChainSafe/ChainBridge/core" msg "github.com/ChainSafe/ChainBridge/message" + "github.com/ChainSafe/ChainBridge/metrics" log "github.com/ChainSafe/log15" "github.com/urfave/cli/v2" ) @@ -188,6 +189,8 @@ func run(ctx *cli.Context) error { c.AddChain(newChain) } + metrics.Start() + c.Start() return nil diff --git a/core/core.go b/core/core.go index cc83ac16e..b13b0a6fe 100644 --- a/core/core.go +++ b/core/core.go @@ -73,3 +73,12 @@ func (c *Core) Start() { func (c *Core) Errors() <-chan error { return c.sysErr } + +// GetChains returns all the chains found in the registry +func (c *Core) GetChains() []Chain { + chains := make([]Chain, 0, len(c.registry)) + for k := range c.registry { + chains = append(chains, c.registry[k]) + } + return chains +} diff --git a/metrics/http.go b/metrics/http.go new file mode 100644 index 000000000..1a4a69dce --- /dev/null +++ b/metrics/http.go @@ -0,0 +1,51 @@ +package metrics + +import ( + "net/http" + "strconv" + + "github.com/ChainSafe/ChainBridge/core" + log "github.com/ChainSafe/log15" +) + +type HttpMetricServer struct { + port int + core *core.Core +} + +type httpMetricOptions struct { + port int + core *core.Core +} + +type chain struct { + id int +} + +func newHTTPMetricServer(opts httpMetricOptions) *HttpMetricServer { + return &HttpMetricServer{ + port: opts.port, + core: opts.core, + } +} + +// Start starts the http metrics server +func (s HttpMetricServer) Start() { + log.Info("Metrics server started", "port", s.port) + + // Setup routes + http.HandleFunc("/health", s.healthStatus) + + // Start http server + // TODO Push strconv to cli parser + http.ListenAndServe(":"+strconv.Itoa(s.port), nil) +} + +func (s HttpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { + + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - Internal Service Error")) + + w.WriteHeader(http.StatusOK) + w.Write([]byte("200 - Operational")) +} diff --git a/metrics/metrics.go b/metrics/metrics.go new file mode 100644 index 000000000..d0fde6941 --- /dev/null +++ b/metrics/metrics.go @@ -0,0 +1,18 @@ +package metrics + +// Start spins up the metrics server +func Start() { + // TODO add to config + httpPort := 8000 + httpEnabled := true + + if httpEnabled { + opts := &httpMetricOptions{ + port: httpPort, + } + httpServer := newHTTPMetricServer(*opts) + httpServer.Start() + } + + // TODO add prometheus +} From 6c36d1b359cd4a66b641f74f6354fa5ca9e08457 Mon Sep 17 00:00:00 2001 From: Stephanie Date: Wed, 19 Aug 2020 14:43:10 -0400 Subject: [PATCH 02/21] Print json strings for generated configs in e2e tests (#509) * prints json strings for generated configs * removed unnessessary output files * go fmt --- e2e/e2e_test.go | 6 +++--- e2e/ethereum/ethereum.go | 17 +++++++++++++++-- e2e/substrate/substrate.go | 16 ++++++++++++++-- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index bd99dccf5..feaea35c5 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -70,19 +70,19 @@ func createAndStartBridge(t *testing.T, name string, contractsA, contractsB *eth // Create logger to write to a file, and store the log file name in global var logger := log.Root().New() sysErr := make(chan error) - ethACfg := eth.CreateConfig(name, EthAChainId, contractsA, eth.EthAEndpoint) + ethACfg := eth.CreateConfig(t, name, EthAChainId, contractsA, eth.EthAEndpoint) ethA, err := ethChain.InitializeChain(ethACfg, logger.New("relayer", name, "chain", "ethA"), sysErr) if err != nil { t.Fatal(err) } - subCfg := sub.CreateConfig(name, SubChainId) + subCfg := sub.CreateConfig(t, name, SubChainId) subA, err := subChain.InitializeChain(subCfg, logger.New("relayer", name, "chain", "sub"), sysErr) if err != nil { t.Fatal(err) } - ethBCfg := eth.CreateConfig(name, EthBChainId, contractsB, eth.EthBEndpoint) + ethBCfg := eth.CreateConfig(t, name, EthBChainId, contractsB, eth.EthBEndpoint) ethB, err := ethChain.InitializeChain(ethBCfg, logger.New("relayer", name, "chain", "ethB"), sysErr) if err != nil { t.Fatal(err) diff --git a/e2e/ethereum/ethereum.go b/e2e/ethereum/ethereum.go index 9456fda97..b75a1aa6a 100644 --- a/e2e/ethereum/ethereum.go +++ b/e2e/ethereum/ethereum.go @@ -5,6 +5,7 @@ package ethereum import ( "context" + "encoding/json" "fmt" "math/big" "os" @@ -55,8 +56,8 @@ type TestContracts struct { AssetStoreEth common.Address // Contract configured for eth to eth generic transfer } -func CreateConfig(key string, chain msg.ChainId, contracts *utils.DeployedContracts, endpoint string) *core.ChainConfig { - return &core.ChainConfig{ +func CreateConfig(t *testing.T, key string, chain msg.ChainId, contracts *utils.DeployedContracts, endpoint string) *core.ChainConfig { + c := &core.ChainConfig{ Name: fmt.Sprintf("ethereum(%s,%d)", key, chain), Id: chain, Endpoint: endpoint, @@ -72,6 +73,18 @@ func CreateConfig(key string, chain msg.ChainId, contracts *utils.DeployedContra "genericHandler": contracts.GenericHandlerAddress.String(), }, } + + json, err := json.Marshal(c) + if err != nil { + t.Fatal(err) + } + + fmt.Println("======================== Ethereum Chain Config ========================") + fmt.Println(string(json)) + fmt.Println("=======================================================================") + + return c + } func DeployTestContracts(t *testing.T, client *utils.Client, endpoint string, id msg.ChainId, threshold *big.Int) *utils.DeployedContracts { diff --git a/e2e/substrate/substrate.go b/e2e/substrate/substrate.go index bb6e54c3a..767cbaea3 100644 --- a/e2e/substrate/substrate.go +++ b/e2e/substrate/substrate.go @@ -4,6 +4,7 @@ package substrate import ( + "encoding/json" "fmt" "os" "testing" @@ -35,8 +36,8 @@ var RelayerSet = []types.AccountID{ types.NewAccountID(DaveKp.AsKeyringPair().PublicKey), } -func CreateConfig(key string, chain msg.ChainId) *core.ChainConfig { - return &core.ChainConfig{ +func CreateConfig(t *testing.T, key string, chain msg.ChainId) *core.ChainConfig { + c := &core.ChainConfig{ Name: fmt.Sprintf("substrate(%s)", key), Id: chain, Endpoint: TestSubEndpoint, @@ -47,6 +48,17 @@ func CreateConfig(key string, chain msg.ChainId) *core.ChainConfig { BlockstorePath: os.TempDir(), Opts: map[string]string{}, } + + json, err := json.Marshal(c) + if err != nil { + t.Fatal(err) + } + + fmt.Println("======================== Substrate Chain Config ========================") + fmt.Println(string(json)) + fmt.Println("========================================================================") + + return c } func WaitForProposalSuccessOrFail(t *testing.T, client *utils.Client, nonce types.U64, chain types.U8) { From 564495f2cbfd6ff106ef795181db78ff138627b0 Mon Sep 17 00:00:00 2001 From: Gregory Markou Date: Wed, 19 Aug 2020 23:11:01 +0300 Subject: [PATCH 03/21] add health check --- blockstore/blockstore.go | 4 ++- chains/ethereum/chain.go | 4 +++ core/chain.go | 3 ++ metrics/http.go | 71 ++++++++++++++++++++++++++++++++++++---- 4 files changed, 75 insertions(+), 7 deletions(-) diff --git a/blockstore/blockstore.go b/blockstore/blockstore.go index 642eb2f04..0c930086b 100644 --- a/blockstore/blockstore.go +++ b/blockstore/blockstore.go @@ -17,6 +17,7 @@ const PathPostfix = ".chainbridge/blockstore" type Blockstorer interface { StoreBlock(*big.Int) error + TryLoadLatestBlock() (*big.Int, error) } var _ Blockstorer = &EmptyStore{} @@ -25,7 +26,8 @@ var _ Blockstorer = &Blockstore{} // Dummy store for testing only type EmptyStore struct{} -func (s *EmptyStore) StoreBlock(_ *big.Int) error { return nil } +func (s *EmptyStore) StoreBlock(_ *big.Int) error { return nil } +func (s *EmptyStore) TryLoadLatestBlock() (*big.Int, error) { return nil, nil } // Blockstore implements Blockstorer. type Blockstore struct { diff --git a/chains/ethereum/chain.go b/chains/ethereum/chain.go index 3ec70258a..0859732d8 100644 --- a/chains/ethereum/chain.go +++ b/chains/ethereum/chain.go @@ -196,6 +196,10 @@ func (c *Chain) Name() string { return c.cfg.Name } +func (c *Chain) GetLatestBlock() (*big.Int, error) { + return c.listener.blockstore.TryLoadLatestBlock() +} + // Stop signals to any running routines to exit func (c *Chain) Stop() { close(c.stop) diff --git a/core/chain.go b/core/chain.go index def795b29..62b808c75 100644 --- a/core/chain.go +++ b/core/chain.go @@ -4,6 +4,8 @@ package core import ( + "math/big" + msg "github.com/ChainSafe/ChainBridge/message" "github.com/ChainSafe/ChainBridge/router" ) @@ -13,6 +15,7 @@ type Chain interface { SetRouter(*router.Router) Id() msg.ChainId Name() string + GetLatestBlock() (*big.Int, error) Stop() } diff --git a/metrics/http.go b/metrics/http.go index 1a4a69dce..05745b17c 100644 --- a/metrics/http.go +++ b/metrics/http.go @@ -1,16 +1,26 @@ package metrics import ( + "fmt" + "math/big" "net/http" "strconv" + "time" "github.com/ChainSafe/ChainBridge/core" + msg "github.com/ChainSafe/ChainBridge/message" log "github.com/ChainSafe/log15" ) type HttpMetricServer struct { - port int - core *core.Core + port int + core *core.Core + blockheights map[msg.ChainId]BlockHeightInfo +} + +type BlockHeightInfo struct { + height *big.Int + lastUpdated time.Time } type httpMetricOptions struct { @@ -24,8 +34,9 @@ type chain struct { func newHTTPMetricServer(opts httpMetricOptions) *HttpMetricServer { return &HttpMetricServer{ - port: opts.port, - core: opts.core, + port: opts.port, + core: opts.core, + blockheights: make(map[msg.ChainId]BlockHeightInfo), } } @@ -41,11 +52,59 @@ func (s HttpMetricServer) Start() { http.ListenAndServe(":"+strconv.Itoa(s.port), nil) } +// healthStatus is a catch-all update that grabs the latest updates on the running chains +// It assumes that the configuration was set correctly, therefore the relevant chains are +// only those that are in the core.Core registry. func (s HttpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("500 - Internal Service Error")) + // Grab all chains + chains := s.core.GetChains() + requestTime := time.Now() + + // Iterate through their block heads and update the cache accordingly + for _, chain := range chains { + latestHeight, err := chain.GetLatestBlock() + if err != nil { + // NOTE this may error if the chain hasn't been polled yet. + // TODO handle re-try + // TODO better error messaging + errorMsg := fmt.Sprintf("%s%d%s%s", "Failed to receive latest head for: ", chain.Id(), "Error:", err) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(errorMsg)) + } + // Get old blockheight + if prevHeight, ok := s.blockheights[chain.Id()]; ok { + // Note The listener performing the polling should already be accounting for the block delay + // TODO Account for timestamps + timeDiff := requestTime.Sub(prevHeight.lastUpdated) + if prevHeight.height.Cmp(latestHeight) >= 0 && timeDiff < 120 { + s.blockheights[chain.Id()] = BlockHeightInfo{ + height: latestHeight, + lastUpdated: requestTime, + } + } else { + if timeDiff.Seconds() > 120 { + // Error if we exceeded the time limit + errorMsg := fmt.Sprintf("%s%s%s%s%s", "Chain height hasn't changed in: ", timeDiff, "Current height", latestHeight) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(errorMsg)) + } else { + // Error for having a smaller blockheight than previous + errorMsg := fmt.Sprintf("%s%s%s%s%s", "latestHeight is <= previousHeight", "previousHeight", prevHeight.height, "latestHeight", latestHeight) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(errorMsg)) + } + } + } else { + // Note: Could be edge case where chain never started, perhaps push initialization to different step + // First time we've received a block for this chain + s.blockheights[chain.Id()] = BlockHeightInfo{ + height: latestHeight, + lastUpdated: requestTime, + } + } + } w.WriteHeader(http.StatusOK) w.Write([]byte("200 - Operational")) } From 796de4692569d8713c2910ca731a9716eaff6af2 Mon Sep 17 00:00:00 2001 From: Stephanie Date: Wed, 19 Aug 2020 16:39:08 -0400 Subject: [PATCH 04/21] Isolate bootstrapping tests for testing purposes (#510) * isolated bootstrap tests * go fmt * make linter happy --- Makefile | 4 + e2e/bootstrap/bootstrap_e2e_test.go | 252 ++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+) create mode 100644 e2e/bootstrap/bootstrap_e2e_test.go diff --git a/Makefile b/Makefile index 1997271c8..22e4c4bfd 100644 --- a/Makefile +++ b/Makefile @@ -79,6 +79,10 @@ test-e2e: @echo " > \033[32mRunning e2e tests...\033[0m " go test -v -timeout 0 ./e2e +test-e2e-bootstrap: + @echo " > \033[32mRunning e2e bootstrap tests...\033[0m " + go test -v -timeout 0 ./e2e/bootstrap + test-eth: @echo " > \033[32mRunning ethereum tests...\033[0m " go test ./chains/ethereum diff --git a/e2e/bootstrap/bootstrap_e2e_test.go b/e2e/bootstrap/bootstrap_e2e_test.go new file mode 100644 index 000000000..fa2b5c458 --- /dev/null +++ b/e2e/bootstrap/bootstrap_e2e_test.go @@ -0,0 +1,252 @@ +// Copyright 2020 ChainSafe Systems +// SPDX-License-Identifier: LGPL-3.0-only + +package e2e + +import ( + "fmt" + "io/ioutil" + "math/big" + "os" + "strings" + "testing" + + ethChain "github.com/ChainSafe/ChainBridge/chains/ethereum" + subChain "github.com/ChainSafe/ChainBridge/chains/substrate" + "github.com/ChainSafe/ChainBridge/core" + eth "github.com/ChainSafe/ChainBridge/e2e/ethereum" + sub "github.com/ChainSafe/ChainBridge/e2e/substrate" + msg "github.com/ChainSafe/ChainBridge/message" + "github.com/ChainSafe/ChainBridge/shared" + ethutils "github.com/ChainSafe/ChainBridge/shared/ethereum" + ethtest "github.com/ChainSafe/ChainBridge/shared/ethereum/testing" + subutils "github.com/ChainSafe/ChainBridge/shared/substrate" + subtest "github.com/ChainSafe/ChainBridge/shared/substrate/testing" + log "github.com/ChainSafe/log15" + "github.com/centrifuge/go-substrate-rpc-client/types" + "github.com/ethereum/go-ethereum/common" +) + +const EthAChainId = msg.ChainId(0) +const SubChainId = msg.ChainId(1) +const EthBChainId = msg.ChainId(2) + +type testContext struct { + ethA *eth.TestContext + ethB *eth.TestContext + subClient *subutils.Client + + EthSubErc20ResourceId msg.ResourceId + EthEthErc20ResourceId msg.ResourceId + EthSubErc721ResourceId msg.ResourceId + EthEthErc721ResourceId msg.ResourceId + GenericHashResourceId msg.ResourceId + EthGenericResourceId msg.ResourceId +} + +func createAndStartBridge(t *testing.T, name string, contractsA, contractsB *ethutils.DeployedContracts) (*core.Core, log.Logger) { + // Create logger to write to a file, and store the log file name in global var + logger := log.Root().New() + sysErr := make(chan error) + ethACfg := eth.CreateConfig(t, name, EthAChainId, contractsA, eth.EthAEndpoint) + ethA, err := ethChain.InitializeChain(ethACfg, logger.New("relayer", name, "chain", "ethA"), sysErr) + if err != nil { + t.Fatal(err) + } + + subCfg := sub.CreateConfig(t, name, SubChainId) + subA, err := subChain.InitializeChain(subCfg, logger.New("relayer", name, "chain", "sub"), sysErr) + if err != nil { + t.Fatal(err) + } + + ethBCfg := eth.CreateConfig(t, name, EthBChainId, contractsB, eth.EthBEndpoint) + ethB, err := ethChain.InitializeChain(ethBCfg, logger.New("relayer", name, "chain", "ethB"), sysErr) + if err != nil { + t.Fatal(err) + } + + bridge := core.NewCore(sysErr) + bridge.AddChain(ethA) + bridge.AddChain(subA) + bridge.AddChain(ethB) + + err = ethA.Start() + if err != nil { + t.Fatal(err) + } + + err = subA.Start() + if err != nil { + t.Fatal(err) + } + + err = ethB.Start() + if err != nil { + t.Fatal(err) + } + + return bridge, logger +} + +func assertChanError(t *testing.T, errs <-chan error) { + select { + case err := <-errs: + t.Fatalf("BridgeA Fatal Error: %s", err) + default: + // Do nothing + fmt.Println("No errors here!") + return + } +} + +func setupFungibleTests(t *testing.T, ctx *testContext) { + mintAmount := big.NewInt(1000) + approveAmount := big.NewInt(500) + + // Deploy Sub<>Eth erc20 on ethA, register resource ID, add handler as minter + erc20ContractASub := ethtest.Erc20DeployMint(t, ctx.ethA.Client, mintAmount) + log.Info("Deployed erc20 contract", "address", erc20ContractASub) + ethtest.Erc20Approve(t, ctx.ethA.Client, erc20ContractASub, ctx.ethA.BaseContracts.ERC20HandlerAddress, approveAmount) + ethtest.Erc20AddMinter(t, ctx.ethA.Client, erc20ContractASub, ctx.ethA.BaseContracts.ERC20HandlerAddress) + ethtest.RegisterResource(t, ctx.ethA.Client, ctx.ethA.BaseContracts.BridgeAddress, ctx.ethA.BaseContracts.ERC20HandlerAddress, ctx.EthSubErc20ResourceId, erc20ContractASub) + ethtest.SetBurnable(t, ctx.ethA.Client, ctx.ethA.BaseContracts.BridgeAddress, ctx.ethA.BaseContracts.ERC20HandlerAddress, erc20ContractASub) + + // Deploy Eth<>Eth erc20 on ethA, register resource ID, add handler as minter + erc20ContractAEth := ethtest.Erc20DeployMint(t, ctx.ethA.Client, mintAmount) + log.Info("Deployed erc20 contract", "address", erc20ContractAEth) + ethtest.Erc20Approve(t, ctx.ethA.Client, erc20ContractAEth, ctx.ethA.BaseContracts.ERC20HandlerAddress, approveAmount) + ethtest.Erc20AddMinter(t, ctx.ethA.Client, erc20ContractAEth, ctx.ethA.BaseContracts.ERC20HandlerAddress) + ethErc20ResourceId := msg.ResourceIdFromSlice(append(common.LeftPadBytes(erc20ContractAEth.Bytes(), 31), 0)) + ethtest.RegisterResource(t, ctx.ethA.Client, ctx.ethA.BaseContracts.BridgeAddress, ctx.ethA.BaseContracts.ERC20HandlerAddress, ethErc20ResourceId, erc20ContractAEth) + ethtest.SetBurnable(t, ctx.ethA.Client, ctx.ethA.BaseContracts.BridgeAddress, ctx.ethA.BaseContracts.ERC20HandlerAddress, erc20ContractAEth) + + // Deploy Eth<>Eth erc20 on ethB, add handler as minter + erc20ContractBEth := ethtest.Erc20DeployMint(t, ctx.ethB.Client, mintAmount) + log.Info("Deployed erc20 contract", "address", erc20ContractBEth) + ethtest.Erc20AddMinter(t, ctx.ethB.Client, erc20ContractBEth, ctx.ethB.BaseContracts.ERC20HandlerAddress) + ethtest.Erc20Approve(t, ctx.ethB.Client, erc20ContractBEth, ctx.ethB.BaseContracts.ERC20HandlerAddress, approveAmount) + ethtest.RegisterResource(t, ctx.ethB.Client, ctx.ethB.BaseContracts.BridgeAddress, ctx.ethB.BaseContracts.ERC20HandlerAddress, ethErc20ResourceId, erc20ContractBEth) + ethtest.SetBurnable(t, ctx.ethB.Client, ctx.ethB.BaseContracts.BridgeAddress, ctx.ethB.BaseContracts.ERC20HandlerAddress, erc20ContractBEth) + + ctx.ethA.TestContracts.Erc20Sub = erc20ContractASub + ctx.ethA.TestContracts.Erc20Eth = erc20ContractAEth + ctx.ethB.TestContracts.Erc20Eth = erc20ContractBEth + ctx.EthEthErc20ResourceId = ethErc20ResourceId +} + +func setupNonFungibleTests(t *testing.T, ctx *testContext) { + + // Deploy Sub<>Eth erc721 on ethA, register resource ID, set burnable + erc721ContractASub := ethtest.Erc721Deploy(t, ctx.ethA.Client) + ethtest.Erc721AddMinter(t, ctx.ethA.Client, erc721ContractASub, ctx.ethA.BaseContracts.ERC721HandlerAddress) + ethtest.RegisterResource(t, ctx.ethA.Client, ctx.ethA.BaseContracts.BridgeAddress, ctx.ethA.BaseContracts.ERC721HandlerAddress, ctx.EthSubErc721ResourceId, erc721ContractASub) + ethtest.SetBurnable(t, ctx.ethA.Client, ctx.ethA.BaseContracts.BridgeAddress, ctx.ethA.BaseContracts.ERC721HandlerAddress, erc721ContractASub) + + // Deploy Eth<>Eth erc721 on ethA, register resource ID, set burnable + erc721ContractAEth := ethtest.Erc721Deploy(t, ctx.ethA.Client) + ethtest.Erc721AddMinter(t, ctx.ethA.Client, erc721ContractAEth, ctx.ethA.BaseContracts.ERC721HandlerAddress) + ethErc721ResourceId := msg.ResourceIdFromSlice(append(common.LeftPadBytes(erc721ContractAEth.Bytes(), 31), byte(EthAChainId))) + ethtest.RegisterResource(t, ctx.ethA.Client, ctx.ethA.BaseContracts.BridgeAddress, ctx.ethA.BaseContracts.ERC721HandlerAddress, ethErc721ResourceId, erc721ContractAEth) + ethtest.SetBurnable(t, ctx.ethA.Client, ctx.ethA.BaseContracts.BridgeAddress, ctx.ethA.BaseContracts.ERC721HandlerAddress, erc721ContractASub) + + // Deploy Eth<>Eth erc721 on ethB, register resource ID, set burnable + erc721ContractBEth := ethtest.Erc721Deploy(t, ctx.ethB.Client) + ethtest.Erc721AddMinter(t, ctx.ethB.Client, erc721ContractBEth, ctx.ethB.BaseContracts.ERC721HandlerAddress) + ethtest.RegisterResource(t, ctx.ethB.Client, ctx.ethB.BaseContracts.BridgeAddress, ctx.ethB.BaseContracts.ERC721HandlerAddress, ethErc721ResourceId, erc721ContractBEth) + ethtest.SetBurnable(t, ctx.ethB.Client, ctx.ethB.BaseContracts.BridgeAddress, ctx.ethB.BaseContracts.ERC721HandlerAddress, erc721ContractBEth) + + ctx.ethA.TestContracts.Erc721Sub = erc721ContractASub + ctx.ethA.TestContracts.Erc721Eth = erc721ContractAEth + ctx.ethB.TestContracts.Erc721Eth = erc721ContractBEth + ctx.EthEthErc721ResourceId = ethErc721ResourceId +} + +func setupGenericTests(t *testing.T, ctx *testContext) { + // Deploy asset store for sub->eth on ethA, register resource ID + assetStoreContractASub := ethtest.DeployAssetStore(t, ctx.ethA.Client) + ethtest.RegisterGenericResource(t, ctx.ethA.Client, ctx.ethA.BaseContracts.BridgeAddress, ctx.ethA.BaseContracts.GenericHandlerAddress, ctx.GenericHashResourceId, assetStoreContractASub, [4]byte{}, ethutils.StoreFunctionSig) + + // Deploy asset store for eth->eth on ethB, register resource ID + assetStoreContractBEth := ethtest.DeployAssetStore(t, ctx.ethB.Client) + ethGenericResourceId := msg.ResourceIdFromSlice(append(common.LeftPadBytes(assetStoreContractBEth.Bytes(), 31), byte(EthBChainId))) + ethtest.RegisterGenericResource(t, ctx.ethB.Client, ctx.ethB.BaseContracts.BridgeAddress, ctx.ethB.BaseContracts.GenericHandlerAddress, ethGenericResourceId, assetStoreContractBEth, [4]byte{}, ethutils.StoreFunctionSig) + // Register resource on ethA as well for deposit, address used could be anything + ethtest.RegisterGenericResource(t, ctx.ethA.Client, ctx.ethA.BaseContracts.BridgeAddress, ctx.ethA.BaseContracts.GenericHandlerAddress, ethGenericResourceId, assetStoreContractBEth, [4]byte{}, ethutils.StoreFunctionSig) + + ctx.ethA.TestContracts.AssetStoreSub = assetStoreContractASub + ctx.ethB.TestContracts.AssetStoreEth = assetStoreContractBEth + ctx.EthGenericResourceId = ethGenericResourceId +} + +// This tests three relayers connected to three chains (2 ethereum, 1 substrate). +// +// EthA: +// - Native erc20 token +// Eth B: +// - Synthetic erc20 token +// Substrate: +// - Synthetic token (native to chain) +// +func Test_ThreeRelayers(t *testing.T) { + shared.SetLogger(log.LvlTrace) + threshold := 3 + + // Setup test client connections for each chain + ethClientA := ethtest.NewClient(t, eth.EthAEndpoint, eth.AliceKp) + ethClientB := ethtest.NewClient(t, eth.EthBEndpoint, eth.AliceKp) + subClient := subtest.CreateClient(t, sub.AliceKp.AsKeyringPair(), sub.TestSubEndpoint) + + // First lookup the substrate resource IDs + var rawRId types.Bytes32 + subtest.QueryConst(t, subClient, "Example", "NativeTokenId", &rawRId) + subErc20ResourceId := msg.ResourceIdFromSlice(rawRId[:]) + subtest.QueryConst(t, subClient, "Example", "Erc721Id", &rawRId) + subErc721ResourceId := msg.ResourceIdFromSlice(rawRId[:]) + subtest.QueryConst(t, subClient, "Example", "HashId", &rawRId) + genericHashResourceId := msg.ResourceIdFromSlice(rawRId[:]) + + // Base setup for ethA + contractsA := eth.DeployTestContracts(t, ethClientA, eth.EthAEndpoint, EthAChainId, big.NewInt(int64(threshold))) + // Base setup for ethB ERC20 - handler can mint + contractsB := eth.DeployTestContracts(t, ethClientB, eth.EthBEndpoint, EthBChainId, big.NewInt(int64(threshold))) + + // Create the initial context, added to in setup functions + ctx := &testContext{ + ethA: ð.TestContext{ + BaseContracts: contractsA, + TestContracts: eth.TestContracts{}, + Client: ethClientA, + }, + ethB: ð.TestContext{ + BaseContracts: contractsB, + TestContracts: eth.TestContracts{}, + Client: ethClientB, + }, + subClient: subClient, + EthSubErc20ResourceId: subErc20ResourceId, + EthSubErc721ResourceId: subErc721ResourceId, + GenericHashResourceId: genericHashResourceId, + } + + setupFungibleTests(t, ctx) + setupNonFungibleTests(t, ctx) + setupGenericTests(t, ctx) + + // Setup substrate client, register resource, add relayers + resources := map[msg.ResourceId]subutils.Method{ + subErc20ResourceId: subutils.ExampleTransferMethod, + subErc721ResourceId: subutils.ExampleMintErc721Method, + genericHashResourceId: subutils.ExampleRemarkMethod, + } + subtest.EnsureInitializedChain(t, subClient, sub.RelayerSet, []msg.ChainId{EthAChainId}, resources, uint32(threshold)) + + // Create and start three bridges with both chains + bridgeA, _ := createAndStartBridge(t, "bob", contractsA, contractsB) + bridgeB, _ := createAndStartBridge(t, "charlie", contractsA, contractsB) + bridgeC, _ := createAndStartBridge(t, "dave", contractsA, contractsB) + + assertChanError(t, bridgeA.Errors()) + assertChanError(t, bridgeB.Errors()) + assertChanError(t, bridgeC.Errors()) +} From c0fa2e7aea27eca7fdedbadc2c8d71de5e1494f1 Mon Sep 17 00:00:00 2001 From: Gregory Markou <16929357+GregTheGreek@users.noreply.github.com> Date: Thu, 20 Aug 2020 02:18:05 +0300 Subject: [PATCH 05/21] Update metrics/http.go Co-authored-by: Miguel Hervas --- metrics/http.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metrics/http.go b/metrics/http.go index 05745b17c..45160543a 100644 --- a/metrics/http.go +++ b/metrics/http.go @@ -78,7 +78,7 @@ func (s HttpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { // Note The listener performing the polling should already be accounting for the block delay // TODO Account for timestamps timeDiff := requestTime.Sub(prevHeight.lastUpdated) - if prevHeight.height.Cmp(latestHeight) >= 0 && timeDiff < 120 { + if latestHeight.Cmp(prevHeight.height) >= 0 && timeDiff < 120 { s.blockheights[chain.Id()] = BlockHeightInfo{ height: latestHeight, lastUpdated: requestTime, From bf89ca4d50a8c7a5cacf518c0d6fc4d388e03f65 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 20 Aug 2020 18:12:49 -0400 Subject: [PATCH 06/21] Refactor files to monitor folder (#512) * refactoring files * changing package names * adding licenses to files * edit comment * adding missing method * removing unused imports --- chains/substrate/chain.go | 5 +++++ cmd/chainbridge/main.go | 4 ++-- e2e/bootstrap/bootstrap_e2e_test.go | 3 --- metrics/metrics.go => monitor/health/health.go | 5 ++++- {metrics => monitor/health}/http.go | 5 ++++- 5 files changed, 15 insertions(+), 7 deletions(-) rename metrics/metrics.go => monitor/health/health.go (74%) rename {metrics => monitor/health}/http.go (97%) diff --git a/chains/substrate/chain.go b/chains/substrate/chain.go index beed65c3a..37841963c 100644 --- a/chains/substrate/chain.go +++ b/chains/substrate/chain.go @@ -31,6 +31,7 @@ import ( msg "github.com/ChainSafe/ChainBridge/message" "github.com/ChainSafe/ChainBridge/router" "github.com/ChainSafe/log15" + "math/big" ) var _ core.Chain = &Chain{} @@ -132,6 +133,10 @@ func (c *Chain) SetRouter(r *router.Router) { c.listener.setRouter(r) } +func (c *Chain) GetLatestBlock() (*big.Int, error) { + return c.listener.blockstore.TryLoadLatestBlock() +} + func (c *Chain) Id() msg.ChainId { return c.cfg.Id } diff --git a/cmd/chainbridge/main.go b/cmd/chainbridge/main.go index f5cc1e190..082b215a1 100644 --- a/cmd/chainbridge/main.go +++ b/cmd/chainbridge/main.go @@ -17,7 +17,7 @@ import ( "github.com/ChainSafe/ChainBridge/config" "github.com/ChainSafe/ChainBridge/core" msg "github.com/ChainSafe/ChainBridge/message" - "github.com/ChainSafe/ChainBridge/metrics" + "github.com/ChainSafe/ChainBridge/monitor/health" log "github.com/ChainSafe/log15" "github.com/urfave/cli/v2" ) @@ -189,7 +189,7 @@ func run(ctx *cli.Context) error { c.AddChain(newChain) } - metrics.Start() + health.Start() c.Start() diff --git a/e2e/bootstrap/bootstrap_e2e_test.go b/e2e/bootstrap/bootstrap_e2e_test.go index fa2b5c458..9fdd86daf 100644 --- a/e2e/bootstrap/bootstrap_e2e_test.go +++ b/e2e/bootstrap/bootstrap_e2e_test.go @@ -5,10 +5,7 @@ package e2e import ( "fmt" - "io/ioutil" "math/big" - "os" - "strings" "testing" ethChain "github.com/ChainSafe/ChainBridge/chains/ethereum" diff --git a/metrics/metrics.go b/monitor/health/health.go similarity index 74% rename from metrics/metrics.go rename to monitor/health/health.go index d0fde6941..16b70e569 100644 --- a/metrics/metrics.go +++ b/monitor/health/health.go @@ -1,4 +1,7 @@ -package metrics +// Copyright 2020 ChainSafe Systems +// SPDX-License-Identifier: LGPL-3.0-only + +package health // Start spins up the metrics server func Start() { diff --git a/metrics/http.go b/monitor/health/http.go similarity index 97% rename from metrics/http.go rename to monitor/health/http.go index 45160543a..c48007020 100644 --- a/metrics/http.go +++ b/monitor/health/http.go @@ -1,4 +1,7 @@ -package metrics +// Copyright 2020 ChainSafe Systems +// SPDX-License-Identifier: LGPL-3.0-only + +package health import ( "fmt" From 6cb08f7c4c1348633ffb6a0d63b1f4c54a900c93 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 22 Aug 2020 13:59:13 -0400 Subject: [PATCH 07/21] Fixing linter issues (#513) * refactoring files * changing package names * adding licenses to files * edit comment * adding missing method * removing unused imports * fixing linter errors for http.go * fixing up linter * removing extra import * handling graceful shutdown --- chains/substrate/chain.go | 3 ++- cmd/chainbridge/account.go | 6 +++--- monitor/health/http.go | 36 +++++++++++++++++++++++++++--------- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/chains/substrate/chain.go b/chains/substrate/chain.go index 37841963c..c5c5a436e 100644 --- a/chains/substrate/chain.go +++ b/chains/substrate/chain.go @@ -24,6 +24,8 @@ As the writer receives messages from the router, it constructs proposals. If a p package substrate import ( + "math/big" + "github.com/ChainSafe/ChainBridge/blockstore" "github.com/ChainSafe/ChainBridge/core" "github.com/ChainSafe/ChainBridge/crypto/sr25519" @@ -31,7 +33,6 @@ import ( msg "github.com/ChainSafe/ChainBridge/message" "github.com/ChainSafe/ChainBridge/router" "github.com/ChainSafe/log15" - "math/big" ) var _ core.Chain = &Chain{} diff --git a/cmd/chainbridge/account.go b/cmd/chainbridge/account.go index 2fbfc67c6..bd79e56af 100644 --- a/cmd/chainbridge/account.go +++ b/cmd/chainbridge/account.go @@ -183,7 +183,7 @@ func importPrivKey(ctx *cli.Context, keytype, datadir, key string, password []by return "", fmt.Errorf("invalid filepath: %s", err) } - file, err := os.OpenFile(fp, os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0600) + file, err := os.OpenFile(filepath.Clean(fp), os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0600) if err != nil { return "", fmt.Errorf("Unable to Open File: %s", err) } @@ -233,7 +233,7 @@ func importEthKey(filename, datadir string, password, newPassword []byte) (strin return "", fmt.Errorf("invalid filepath: %s", err) } - file, err := os.OpenFile(fp, os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0600) + file, err := os.OpenFile(filepath.Clean(fp), os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0600) if err != nil { return "", err } @@ -374,7 +374,7 @@ func generateKeypair(keytype, datadir string, password []byte, subNetwork string return "", fmt.Errorf("invalid filepath: %s", err) } - file, err := os.OpenFile(fp, os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0600) + file, err := os.OpenFile(filepath.Clean(fp), os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0600) if err != nil { return "", err } diff --git a/monitor/health/http.go b/monitor/health/http.go index c48007020..24c81ca22 100644 --- a/monitor/health/http.go +++ b/monitor/health/http.go @@ -31,9 +31,9 @@ type httpMetricOptions struct { core *core.Core } -type chain struct { - id int -} +//type chain struct { +// id int +//} func newHTTPMetricServer(opts httpMetricOptions) *HttpMetricServer { return &HttpMetricServer{ @@ -52,7 +52,13 @@ func (s HttpMetricServer) Start() { // Start http server // TODO Push strconv to cli parser - http.ListenAndServe(":"+strconv.Itoa(s.port), nil) + err := http.ListenAndServe(":"+strconv.Itoa(s.port), nil) + + if err == http.ErrServerClosed { + log.Info("Server is shutting down", err) + } else { + log.Error("Shutting down, server error: ", err) + } } // healthStatus is a catch-all update that grabs the latest updates on the running chains @@ -73,7 +79,10 @@ func (s HttpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { // TODO better error messaging errorMsg := fmt.Sprintf("%s%d%s%s", "Failed to receive latest head for: ", chain.Id(), "Error:", err) w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(errorMsg)) + _, err = w.Write([]byte(errorMsg)) + if err != nil { + log.Error("Failed to write, failed to get latest height and writing error", err) + } } // Get old blockheight @@ -89,14 +98,20 @@ func (s HttpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { } else { if timeDiff.Seconds() > 120 { // Error if we exceeded the time limit - errorMsg := fmt.Sprintf("%s%s%s%s%s", "Chain height hasn't changed in: ", timeDiff, "Current height", latestHeight) + errorMsg := fmt.Sprintf("%s%s%s%s", "Chain height hasn't changed in: ", timeDiff, "Current height", latestHeight) w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(errorMsg)) + _, err = w.Write([]byte(errorMsg)) + if err != nil { + log.Error("Failed to write, chain height hasn't changed in: %d seconds, Current height: %d", timeDiff.Seconds(), latestHeight, err) + } } else { // Error for having a smaller blockheight than previous errorMsg := fmt.Sprintf("%s%s%s%s%s", "latestHeight is <= previousHeight", "previousHeight", prevHeight.height, "latestHeight", latestHeight) w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(errorMsg)) + _, err := w.Write([]byte(errorMsg)) + if err != nil { + log.Error("Failed to write, latest height less than previous height, latest height: %d, previous height: %d", latestHeight, prevHeight.height, err) + } } } } else { @@ -109,5 +124,8 @@ func (s HttpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { } } w.WriteHeader(http.StatusOK) - w.Write([]byte("200 - Operational")) + _, err := w.Write([]byte("200 - Operational")) + if err != nil { + log.Error("Failed to write, 200 - Operational") + } } From f3eaeb1558c5f4b902b0025e68516de85d4ba5d5 Mon Sep 17 00:00:00 2001 From: Gregory Markou Date: Mon, 24 Aug 2020 18:34:55 +0300 Subject: [PATCH 08/21] rename structs --- metrics/http.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/metrics/http.go b/metrics/http.go index 45160543a..2dbd8f688 100644 --- a/metrics/http.go +++ b/metrics/http.go @@ -12,13 +12,13 @@ import ( log "github.com/ChainSafe/log15" ) -type HttpMetricServer struct { +type httpMetricServer struct { port int core *core.Core - blockheights map[msg.ChainId]BlockHeightInfo + blockheights map[msg.ChainId]blockHeightInfo } -type BlockHeightInfo struct { +type blockHeightInfo struct { height *big.Int lastUpdated time.Time } @@ -32,16 +32,16 @@ type chain struct { id int } -func newHTTPMetricServer(opts httpMetricOptions) *HttpMetricServer { - return &HttpMetricServer{ +func newhttpMetricServer(opts httpMetricOptions) *httpMetricServer { + return &httpMetricServer{ port: opts.port, core: opts.core, - blockheights: make(map[msg.ChainId]BlockHeightInfo), + blockheights: make(map[msg.ChainId]blockHeightInfo), } } // Start starts the http metrics server -func (s HttpMetricServer) Start() { +func (s httpMetricServer) Start() { log.Info("Metrics server started", "port", s.port) // Setup routes @@ -55,7 +55,7 @@ func (s HttpMetricServer) Start() { // healthStatus is a catch-all update that grabs the latest updates on the running chains // It assumes that the configuration was set correctly, therefore the relevant chains are // only those that are in the core.Core registry. -func (s HttpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { +func (s httpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { // Grab all chains chains := s.core.GetChains() @@ -79,7 +79,7 @@ func (s HttpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { // TODO Account for timestamps timeDiff := requestTime.Sub(prevHeight.lastUpdated) if latestHeight.Cmp(prevHeight.height) >= 0 && timeDiff < 120 { - s.blockheights[chain.Id()] = BlockHeightInfo{ + s.blockheights[chain.Id()] = blockHeightInfo{ height: latestHeight, lastUpdated: requestTime, } @@ -99,7 +99,7 @@ func (s HttpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { } else { // Note: Could be edge case where chain never started, perhaps push initialization to different step // First time we've received a block for this chain - s.blockheights[chain.Id()] = BlockHeightInfo{ + s.blockheights[chain.Id()] = blockHeightInfo{ height: latestHeight, lastUpdated: requestTime, } From 80f095c8ca9d4dfca54fdebb9f389afa6d0770f1 Mon Sep 17 00:00:00 2001 From: Gregory Markou Date: Mon, 24 Aug 2020 18:48:36 +0300 Subject: [PATCH 09/21] cleanup files --- monitor/health/health.go | 6 ++---- monitor/health/http.go | 7 ------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/monitor/health/health.go b/monitor/health/health.go index 16b70e569..1efc54dae 100644 --- a/monitor/health/health.go +++ b/monitor/health/health.go @@ -5,7 +5,7 @@ package health // Start spins up the metrics server func Start() { - // TODO add to config + // TODO add to config file httpPort := 8000 httpEnabled := true @@ -13,9 +13,7 @@ func Start() { opts := &httpMetricOptions{ port: httpPort, } - httpServer := newHTTPMetricServer(*opts) + httpServer := newhttpMetricServer(*opts) httpServer.Start() } - - // TODO add prometheus } diff --git a/monitor/health/http.go b/monitor/health/http.go index 1c2c59de0..7a16a7b76 100644 --- a/monitor/health/http.go +++ b/monitor/health/http.go @@ -31,10 +31,6 @@ type httpMetricOptions struct { core *core.Core } -//type chain struct { -// id int -//} - func newhttpMetricServer(opts httpMetricOptions) *httpMetricServer { return &httpMetricServer{ port: opts.port, @@ -51,7 +47,6 @@ func (s httpMetricServer) Start() { http.HandleFunc("/health", s.healthStatus) // Start http server - // TODO Push strconv to cli parser err := http.ListenAndServe(":"+strconv.Itoa(s.port), nil) if err == http.ErrServerClosed { @@ -74,8 +69,6 @@ func (s httpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { for _, chain := range chains { latestHeight, err := chain.GetLatestBlock() if err != nil { - // NOTE this may error if the chain hasn't been polled yet. - // TODO handle re-try // TODO better error messaging errorMsg := fmt.Sprintf("%s%d%s%s", "Failed to receive latest head for: ", chain.Id(), "Error:", err) w.WriteHeader(http.StatusInternalServerError) From 75a0b1d5b3f7e5fdaa9795ce4cf416ff753cab21 Mon Sep 17 00:00:00 2001 From: Gregory Markou Date: Mon, 24 Aug 2020 21:20:07 +0300 Subject: [PATCH 10/21] remove use of blockstore --- chains/ethereum/chain.go | 2 +- chains/ethereum/listener.go | 2 ++ chains/substrate/listener.go | 2 ++ cmd/chainbridge/main.go | 4 ++- e2e-test-configs/config0.json | 21 +++++++++++++ e2e-test-configs/config1.json | 38 ++++++++++++++++++++++++ e2e-test-configs/config2.json | 38 ++++++++++++++++++++++++ monitor/health/health.go | 9 ++++-- monitor/health/http.go | 55 +++++++++++++++++++++++++---------- 9 files changed, 151 insertions(+), 20 deletions(-) create mode 100644 e2e-test-configs/config0.json create mode 100644 e2e-test-configs/config1.json create mode 100644 e2e-test-configs/config2.json diff --git a/chains/ethereum/chain.go b/chains/ethereum/chain.go index 0859732d8..0a2039bbd 100644 --- a/chains/ethereum/chain.go +++ b/chains/ethereum/chain.go @@ -197,7 +197,7 @@ func (c *Chain) Name() string { } func (c *Chain) GetLatestBlock() (*big.Int, error) { - return c.listener.blockstore.TryLoadLatestBlock() + return c.listener.latestBlock } // Stop signals to any running routines to exit diff --git a/chains/ethereum/listener.go b/chains/ethereum/listener.go index 42ae53881..1f8480d3a 100644 --- a/chains/ethereum/listener.go +++ b/chains/ethereum/listener.go @@ -41,6 +41,7 @@ type listener struct { blockstore blockstore.Blockstorer stop <-chan int sysErr chan<- error // Reports fatal error to core + latestBlock *big.Int } // NewListener creates and returns a listener @@ -132,6 +133,7 @@ func (l *listener) pollBlocks() error { // Goto next block and reset retry counter currentBlock.Add(currentBlock, big.NewInt(1)) + l.latestBlock = currentBlock retry = BlockRetryLimit } } diff --git a/chains/substrate/listener.go b/chains/substrate/listener.go index fd0593782..16de569ad 100644 --- a/chains/substrate/listener.go +++ b/chains/substrate/listener.go @@ -28,6 +28,7 @@ type listener struct { log log15.Logger stop <-chan int sysErr chan<- error + latestBlock *big.Int } // Frequency of polling for a new block @@ -159,6 +160,7 @@ func (l *listener) pollBlocks() error { } currentBlock++ + l.latestBlock = big.NewInt(0).SetUint64(currentBlock) retry = BlockRetryLimit } } diff --git a/cmd/chainbridge/main.go b/cmd/chainbridge/main.go index 082b215a1..acda31262 100644 --- a/cmd/chainbridge/main.go +++ b/cmd/chainbridge/main.go @@ -189,7 +189,9 @@ func run(ctx *cli.Context) error { c.AddChain(newChain) } - health.Start() + go func() { + health.Start(c) + }() c.Start() diff --git a/e2e-test-configs/config0.json b/e2e-test-configs/config0.json new file mode 100644 index 000000000..43bc857ae --- /dev/null +++ b/e2e-test-configs/config0.json @@ -0,0 +1,21 @@ +{ + "Chains": [ + { + "name": "goerli", + "type": "ethereum", + "id": "1", + "endpoint": "ws://localhost:8545", + "from": "0x02642514dbebb135043cdbae4599a04a2ea84c9bcd701575d77d1a4c7cb2b977e0", + "opts": { + "bridge": "0x62877dDCd49aD22f5eDfc6ac108e9a4b5D2bD88B", + "erc20Handler": "0x3167776db165D8eA0f51790CA2bbf44Db5105ADF", + "erc721Handler": "0x3f709398808af36ADBA86ACC617FeB7F5B7B193E", + "genericHandler": "0x2B6Ab4b880A45a07d83Cf4d664Df4Ab85705Bc07", + "gasLimit": "1000000", + "maxGasPrice": "20000000", + "startBlock": "0", + "http": "false" + } + } + ] +} \ No newline at end of file diff --git a/e2e-test-configs/config1.json b/e2e-test-configs/config1.json new file mode 100644 index 000000000..5ac5ac1c9 --- /dev/null +++ b/e2e-test-configs/config1.json @@ -0,0 +1,38 @@ +{ + "Chains": [ + { + "name": "goerli", + "type": "ethereum", + "id": "1", + "endpoint": "http://localhost:8545", + "from": "0x8e0a907331554AF72563Bd8D43051C2E64Be5d35", + "opts": { + "bridge": "0x62877dDCd49aD22f5eDfc6ac108e9a4b5D2bD88B", + "erc20Handler": "0x3167776db165D8eA0f51790CA2bbf44Db5105ADF", + "erc721Handler": "0x3f709398808af36ADBA86ACC617FeB7F5B7B193E", + "genericHandler": "0x2B6Ab4b880A45a07d83Cf4d664Df4Ab85705Bc07", + "gasLimit": "1000000", + "gasPrice": "20000000", + "startBlock": "0", + "http": "false" + } + }, + { + "name": "kotti", + "type": "ethereum", + "id": "2", + "endpoint": "http://localhost:8546", + "from": "0x8e0a907331554AF72563Bd8D43051C2E64Be5d35", + "opts": { + "bridge": "0x62877dDCd49aD22f5eDfc6ac108e9a4b5D2bD88B", + "erc20Handler": "0x3167776db165D8eA0f51790CA2bbf44Db5105ADF", + "erc721Handler": "0x3f709398808af36ADBA86ACC617FeB7F5B7B193E", + "genericHandler": "0x2B6Ab4b880A45a07d83Cf4d664Df4Ab85705Bc07", + "gasLimit": "1000000", + "gasPrice": "20000000", + "startBlock": "0", + "http": "true" + } + } + ] +} \ No newline at end of file diff --git a/e2e-test-configs/config2.json b/e2e-test-configs/config2.json new file mode 100644 index 000000000..1fe437def --- /dev/null +++ b/e2e-test-configs/config2.json @@ -0,0 +1,38 @@ +{ + "Chains": [ + { + "name": "goerli", + "type": "ethereum", + "id": "1", + "endpoint": "http://localhost:8545", + "from": "0x24962717f8fA5BA3b931bACaF9ac03924EB475a0", + "opts": { + "bridge": "0x62877dDCd49aD22f5eDfc6ac108e9a4b5D2bD88B", + "erc20Handler": "0x3167776db165D8eA0f51790CA2bbf44Db5105ADF", + "erc721Handler": "0x3f709398808af36ADBA86ACC617FeB7F5B7B193E", + "genericHandler": "0x2B6Ab4b880A45a07d83Cf4d664Df4Ab85705Bc07", + "gasLimit": "1000000", + "gasPrice": "20000000", + "startBlock": "0", + "http": "false" + } + }, + { + "name": "kotti", + "type": "ethereum", + "id": "2", + "endpoint": "http://localhost:8546", + "from": "0x24962717f8fA5BA3b931bACaF9ac03924EB475a0", + "opts": { + "bridge": "0x62877dDCd49aD22f5eDfc6ac108e9a4b5D2bD88B", + "erc20Handler": "0x3167776db165D8eA0f51790CA2bbf44Db5105ADF", + "erc721Handler": "0x3f709398808af36ADBA86ACC617FeB7F5B7B193E", + "genericHandler": "0x2B6Ab4b880A45a07d83Cf4d664Df4Ab85705Bc07", + "gasLimit": "1000000", + "gasPrice": "20000000", + "startBlock": "0", + "http": "true" + } + } + ] +} \ No newline at end of file diff --git a/monitor/health/health.go b/monitor/health/health.go index 1efc54dae..6e3f7abdf 100644 --- a/monitor/health/health.go +++ b/monitor/health/health.go @@ -3,15 +3,20 @@ package health +import "github.com/ChainSafe/ChainBridge/core" + // Start spins up the metrics server -func Start() { +func Start(core *core.Core) { // TODO add to config file httpPort := 8000 + blockTimeDelay := 10 httpEnabled := true if httpEnabled { opts := &httpMetricOptions{ - port: httpPort, + port: httpPort, + timeDelay: blockTimeDelay, + core: core, } httpServer := newhttpMetricServer(*opts) httpServer.Start() diff --git a/monitor/health/http.go b/monitor/health/http.go index 7a16a7b76..662a34b77 100644 --- a/monitor/health/http.go +++ b/monitor/health/http.go @@ -4,6 +4,7 @@ package health import ( + "encoding/json" "fmt" "math/big" "net/http" @@ -17,6 +18,7 @@ import ( type httpMetricServer struct { port int + timeDelay int core *core.Core blockheights map[msg.ChainId]blockHeightInfo } @@ -27,14 +29,21 @@ type blockHeightInfo struct { } type httpMetricOptions struct { - port int - core *core.Core + port int + timeDelay int + core *core.Core +} + +type httpResponse struct { + msg string + error string } func newhttpMetricServer(opts httpMetricOptions) *httpMetricServer { return &httpMetricServer{ port: opts.port, core: opts.core, + timeDelay: opts.timeDelay, blockheights: make(map[msg.ChainId]blockHeightInfo), } } @@ -60,6 +69,7 @@ func (s httpMetricServer) Start() { // It assumes that the configuration was set correctly, therefore the relevant chains are // only those that are in the core.Core registry. func (s httpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") // Grab all chains chains := s.core.GetChains() @@ -70,9 +80,12 @@ func (s httpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { latestHeight, err := chain.GetLatestBlock() if err != nil { // TODO better error messaging - errorMsg := fmt.Sprintf("%s%d%s%s", "Failed to receive latest head for: ", chain.Id(), "Error:", err) + response := &httpResponse{ + msg: "", + error: fmt.Sprintf("%s%d%s%s", "Failed to receive latest head for: ", chain.Id(), "Error:", err), + } w.WriteHeader(http.StatusInternalServerError) - _, err = w.Write([]byte(errorMsg)) + err := json.NewEncoder(w).Encode(response) if err != nil { log.Error("Failed to write, failed to get latest height and writing error", err) } @@ -83,25 +96,31 @@ func (s httpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { // Note The listener performing the polling should already be accounting for the block delay // TODO Account for timestamps timeDiff := requestTime.Sub(prevHeight.lastUpdated) - if latestHeight.Cmp(prevHeight.height) >= 0 && timeDiff < 120 { + if latestHeight.Cmp(prevHeight.height) >= 0 && int(timeDiff.Seconds()) < s.timeDelay { s.blockheights[chain.Id()] = blockHeightInfo{ height: latestHeight, lastUpdated: requestTime, } } else { - if timeDiff.Seconds() > 120 { + if int(timeDiff.Seconds()) > s.timeDelay { // Error if we exceeded the time limit - errorMsg := fmt.Sprintf("%s%s%s%s", "Chain height hasn't changed in: ", timeDiff, "Current height", latestHeight) + response := &httpResponse{ + msg: "", + error: fmt.Sprintf("%s%s%s%s", "Chain height hasn't changed in: ", timeDiff, " Current height", latestHeight), + } w.WriteHeader(http.StatusInternalServerError) - _, err = w.Write([]byte(errorMsg)) + err := json.NewEncoder(w).Encode(response) if err != nil { log.Error("Failed to write, chain height hasn't changed in: %d seconds, Current height: %d", timeDiff.Seconds(), latestHeight, err) } } else { // Error for having a smaller blockheight than previous - errorMsg := fmt.Sprintf("%s%s%s%s%s", "latestHeight is <= previousHeight", "previousHeight", prevHeight.height, "latestHeight", latestHeight) - w.WriteHeader(http.StatusInternalServerError) - _, err := w.Write([]byte(errorMsg)) + response := &httpResponse{ + msg: "", + error: fmt.Sprintf("%s%s%s%s%s", "latestHeight is <= previousHeight", "previousHeight", prevHeight.height, "latestHeight", latestHeight), + } + w.Header.Set(http.StatusInternalServerError) + err := json.NewEncoder(w).Encode(response) if err != nil { log.Error("Failed to write, latest height less than previous height, latest height: %d, previous height: %d", latestHeight, prevHeight.height, err) } @@ -115,10 +134,14 @@ func (s httpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { lastUpdated: requestTime, } } - } - w.WriteHeader(http.StatusOK) - _, err := w.Write([]byte("200 - Operational")) - if err != nil { - log.Error("Failed to write, 200 - Operational") + response := &httpResponse{ + msg: "200 - operational", + error: "", + } + w.WriteHeader(http.StatusOK) + err = json.NewEncoder(w).Encode(response) + if err != nil { + log.Error("Failed to write, 200 - Operational") + } } } From f411669744ae27cd75cd6c2a828c92a82df90df5 Mon Sep 17 00:00:00 2001 From: Gregory Markou Date: Mon, 24 Aug 2020 21:52:17 +0300 Subject: [PATCH 11/21] fix headers --- chains/ethereum/chain.go | 2 +- chains/substrate/chain.go | 4 ++-- core/chain.go | 2 +- monitor/health/health.go | 2 +- monitor/health/http.go | 49 ++++++++++++++++----------------------- 5 files changed, 25 insertions(+), 34 deletions(-) diff --git a/chains/ethereum/chain.go b/chains/ethereum/chain.go index 0a2039bbd..cb76d7155 100644 --- a/chains/ethereum/chain.go +++ b/chains/ethereum/chain.go @@ -196,7 +196,7 @@ func (c *Chain) Name() string { return c.cfg.Name } -func (c *Chain) GetLatestBlock() (*big.Int, error) { +func (c *Chain) GetLatestBlock() *big.Int { return c.listener.latestBlock } diff --git a/chains/substrate/chain.go b/chains/substrate/chain.go index c5c5a436e..f64a0cba0 100644 --- a/chains/substrate/chain.go +++ b/chains/substrate/chain.go @@ -134,8 +134,8 @@ func (c *Chain) SetRouter(r *router.Router) { c.listener.setRouter(r) } -func (c *Chain) GetLatestBlock() (*big.Int, error) { - return c.listener.blockstore.TryLoadLatestBlock() +func (c *Chain) GetLatestBlock() *big.Int { + return c.listener.latestBlock } func (c *Chain) Id() msg.ChainId { diff --git a/core/chain.go b/core/chain.go index 62b808c75..f2b9d9271 100644 --- a/core/chain.go +++ b/core/chain.go @@ -15,7 +15,7 @@ type Chain interface { SetRouter(*router.Router) Id() msg.ChainId Name() string - GetLatestBlock() (*big.Int, error) + GetLatestBlock() *big.Int Stop() } diff --git a/monitor/health/health.go b/monitor/health/health.go index 6e3f7abdf..f0b9c7bf4 100644 --- a/monitor/health/health.go +++ b/monitor/health/health.go @@ -9,7 +9,7 @@ import "github.com/ChainSafe/ChainBridge/core" func Start(core *core.Core) { // TODO add to config file httpPort := 8000 - blockTimeDelay := 10 + blockTimeDelay := 120 httpEnabled := true if httpEnabled { diff --git a/monitor/health/http.go b/monitor/health/http.go index 662a34b77..5ee9f6d5e 100644 --- a/monitor/health/http.go +++ b/monitor/health/http.go @@ -35,8 +35,8 @@ type httpMetricOptions struct { } type httpResponse struct { - msg string - error string + Data string + Error string } func newhttpMetricServer(opts httpMetricOptions) *httpMetricServer { @@ -77,19 +77,7 @@ func (s httpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { // Iterate through their block heads and update the cache accordingly for _, chain := range chains { - latestHeight, err := chain.GetLatestBlock() - if err != nil { - // TODO better error messaging - response := &httpResponse{ - msg: "", - error: fmt.Sprintf("%s%d%s%s", "Failed to receive latest head for: ", chain.Id(), "Error:", err), - } - w.WriteHeader(http.StatusInternalServerError) - err := json.NewEncoder(w).Encode(response) - if err != nil { - log.Error("Failed to write, failed to get latest height and writing error", err) - } - } + latestHeight := chain.GetLatestBlock() // Get old blockheight if prevHeight, ok := s.blockheights[chain.Id()]; ok { @@ -101,29 +89,32 @@ func (s httpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { height: latestHeight, lastUpdated: requestTime, } + return } else { if int(timeDiff.Seconds()) > s.timeDelay { // Error if we exceeded the time limit response := &httpResponse{ - msg: "", - error: fmt.Sprintf("%s%s%s%s", "Chain height hasn't changed in: ", timeDiff, " Current height", latestHeight), + Data: "", + Error: fmt.Sprintf("%s%s%s%s", "Chain height hasn't changed in: ", timeDiff, " Current height: ", latestHeight), } w.WriteHeader(http.StatusInternalServerError) err := json.NewEncoder(w).Encode(response) if err != nil { log.Error("Failed to write, chain height hasn't changed in: %d seconds, Current height: %d", timeDiff.Seconds(), latestHeight, err) } + return } else { // Error for having a smaller blockheight than previous response := &httpResponse{ - msg: "", - error: fmt.Sprintf("%s%s%s%s%s", "latestHeight is <= previousHeight", "previousHeight", prevHeight.height, "latestHeight", latestHeight), + Data: "", + Error: fmt.Sprintf("%s%s%s%s%s", "latestHeight is <= previousHeight ", "previousHeight: ", prevHeight.height, "latestHeight: ", latestHeight), } - w.Header.Set(http.StatusInternalServerError) + w.WriteHeader(http.StatusInternalServerError) err := json.NewEncoder(w).Encode(response) if err != nil { log.Error("Failed to write, latest height less than previous height, latest height: %d, previous height: %d", latestHeight, prevHeight.height, err) } + return } } } else { @@ -134,14 +125,14 @@ func (s httpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { lastUpdated: requestTime, } } - response := &httpResponse{ - msg: "200 - operational", - error: "", - } - w.WriteHeader(http.StatusOK) - err = json.NewEncoder(w).Encode(response) - if err != nil { - log.Error("Failed to write, 200 - Operational") - } + } + response := &httpResponse{ + Data: "200 - operational", + Error: "", + } + w.WriteHeader(http.StatusOK) + err := json.NewEncoder(w).Encode(response) + if err != nil { + log.Error("Failed to write: 200 - Operational") } } From b16a8569e687c517fdb772b5da8bbffc7ccd139f Mon Sep 17 00:00:00 2001 From: Gregory Markou <16929357+GregTheGreek@users.noreply.github.com> Date: Tue, 25 Aug 2020 13:55:56 +0300 Subject: [PATCH 12/21] Update monitor/health/health.go Co-authored-by: David Ansermino --- monitor/health/health.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monitor/health/health.go b/monitor/health/health.go index f0b9c7bf4..a6809a0cf 100644 --- a/monitor/health/health.go +++ b/monitor/health/health.go @@ -13,12 +13,12 @@ func Start(core *core.Core) { httpEnabled := true if httpEnabled { - opts := &httpMetricOptions{ + opts := httpMetricOptions{ port: httpPort, timeDelay: blockTimeDelay, core: core, } - httpServer := newhttpMetricServer(*opts) + httpServer := newhttpMetricServer(opts) httpServer.Start() } } From 76451d027898c4dffea6a09902dc07f326989df4 Mon Sep 17 00:00:00 2001 From: Gregory Markou Date: Tue, 25 Aug 2020 13:59:06 +0300 Subject: [PATCH 13/21] some updates --- Makefile | 4 - blockstore/blockstore.go | 4 +- e2e-test-configs/config0.json | 21 --- e2e-test-configs/config1.json | 38 ----- e2e-test-configs/config2.json | 38 ----- e2e/bootstrap/bootstrap_e2e_test.go | 249 ---------------------------- e2e/ethereum/ethereum.go | 15 +- e2e/substrate/substrate.go | 14 +- 8 files changed, 3 insertions(+), 380 deletions(-) delete mode 100644 e2e-test-configs/config0.json delete mode 100644 e2e-test-configs/config1.json delete mode 100644 e2e-test-configs/config2.json delete mode 100644 e2e/bootstrap/bootstrap_e2e_test.go diff --git a/Makefile b/Makefile index 22e4c4bfd..1997271c8 100644 --- a/Makefile +++ b/Makefile @@ -79,10 +79,6 @@ test-e2e: @echo " > \033[32mRunning e2e tests...\033[0m " go test -v -timeout 0 ./e2e -test-e2e-bootstrap: - @echo " > \033[32mRunning e2e bootstrap tests...\033[0m " - go test -v -timeout 0 ./e2e/bootstrap - test-eth: @echo " > \033[32mRunning ethereum tests...\033[0m " go test ./chains/ethereum diff --git a/blockstore/blockstore.go b/blockstore/blockstore.go index 0c930086b..642eb2f04 100644 --- a/blockstore/blockstore.go +++ b/blockstore/blockstore.go @@ -17,7 +17,6 @@ const PathPostfix = ".chainbridge/blockstore" type Blockstorer interface { StoreBlock(*big.Int) error - TryLoadLatestBlock() (*big.Int, error) } var _ Blockstorer = &EmptyStore{} @@ -26,8 +25,7 @@ var _ Blockstorer = &Blockstore{} // Dummy store for testing only type EmptyStore struct{} -func (s *EmptyStore) StoreBlock(_ *big.Int) error { return nil } -func (s *EmptyStore) TryLoadLatestBlock() (*big.Int, error) { return nil, nil } +func (s *EmptyStore) StoreBlock(_ *big.Int) error { return nil } // Blockstore implements Blockstorer. type Blockstore struct { diff --git a/e2e-test-configs/config0.json b/e2e-test-configs/config0.json deleted file mode 100644 index 43bc857ae..000000000 --- a/e2e-test-configs/config0.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "Chains": [ - { - "name": "goerli", - "type": "ethereum", - "id": "1", - "endpoint": "ws://localhost:8545", - "from": "0x02642514dbebb135043cdbae4599a04a2ea84c9bcd701575d77d1a4c7cb2b977e0", - "opts": { - "bridge": "0x62877dDCd49aD22f5eDfc6ac108e9a4b5D2bD88B", - "erc20Handler": "0x3167776db165D8eA0f51790CA2bbf44Db5105ADF", - "erc721Handler": "0x3f709398808af36ADBA86ACC617FeB7F5B7B193E", - "genericHandler": "0x2B6Ab4b880A45a07d83Cf4d664Df4Ab85705Bc07", - "gasLimit": "1000000", - "maxGasPrice": "20000000", - "startBlock": "0", - "http": "false" - } - } - ] -} \ No newline at end of file diff --git a/e2e-test-configs/config1.json b/e2e-test-configs/config1.json deleted file mode 100644 index 5ac5ac1c9..000000000 --- a/e2e-test-configs/config1.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "Chains": [ - { - "name": "goerli", - "type": "ethereum", - "id": "1", - "endpoint": "http://localhost:8545", - "from": "0x8e0a907331554AF72563Bd8D43051C2E64Be5d35", - "opts": { - "bridge": "0x62877dDCd49aD22f5eDfc6ac108e9a4b5D2bD88B", - "erc20Handler": "0x3167776db165D8eA0f51790CA2bbf44Db5105ADF", - "erc721Handler": "0x3f709398808af36ADBA86ACC617FeB7F5B7B193E", - "genericHandler": "0x2B6Ab4b880A45a07d83Cf4d664Df4Ab85705Bc07", - "gasLimit": "1000000", - "gasPrice": "20000000", - "startBlock": "0", - "http": "false" - } - }, - { - "name": "kotti", - "type": "ethereum", - "id": "2", - "endpoint": "http://localhost:8546", - "from": "0x8e0a907331554AF72563Bd8D43051C2E64Be5d35", - "opts": { - "bridge": "0x62877dDCd49aD22f5eDfc6ac108e9a4b5D2bD88B", - "erc20Handler": "0x3167776db165D8eA0f51790CA2bbf44Db5105ADF", - "erc721Handler": "0x3f709398808af36ADBA86ACC617FeB7F5B7B193E", - "genericHandler": "0x2B6Ab4b880A45a07d83Cf4d664Df4Ab85705Bc07", - "gasLimit": "1000000", - "gasPrice": "20000000", - "startBlock": "0", - "http": "true" - } - } - ] -} \ No newline at end of file diff --git a/e2e-test-configs/config2.json b/e2e-test-configs/config2.json deleted file mode 100644 index 1fe437def..000000000 --- a/e2e-test-configs/config2.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "Chains": [ - { - "name": "goerli", - "type": "ethereum", - "id": "1", - "endpoint": "http://localhost:8545", - "from": "0x24962717f8fA5BA3b931bACaF9ac03924EB475a0", - "opts": { - "bridge": "0x62877dDCd49aD22f5eDfc6ac108e9a4b5D2bD88B", - "erc20Handler": "0x3167776db165D8eA0f51790CA2bbf44Db5105ADF", - "erc721Handler": "0x3f709398808af36ADBA86ACC617FeB7F5B7B193E", - "genericHandler": "0x2B6Ab4b880A45a07d83Cf4d664Df4Ab85705Bc07", - "gasLimit": "1000000", - "gasPrice": "20000000", - "startBlock": "0", - "http": "false" - } - }, - { - "name": "kotti", - "type": "ethereum", - "id": "2", - "endpoint": "http://localhost:8546", - "from": "0x24962717f8fA5BA3b931bACaF9ac03924EB475a0", - "opts": { - "bridge": "0x62877dDCd49aD22f5eDfc6ac108e9a4b5D2bD88B", - "erc20Handler": "0x3167776db165D8eA0f51790CA2bbf44Db5105ADF", - "erc721Handler": "0x3f709398808af36ADBA86ACC617FeB7F5B7B193E", - "genericHandler": "0x2B6Ab4b880A45a07d83Cf4d664Df4Ab85705Bc07", - "gasLimit": "1000000", - "gasPrice": "20000000", - "startBlock": "0", - "http": "true" - } - } - ] -} \ No newline at end of file diff --git a/e2e/bootstrap/bootstrap_e2e_test.go b/e2e/bootstrap/bootstrap_e2e_test.go deleted file mode 100644 index 9fdd86daf..000000000 --- a/e2e/bootstrap/bootstrap_e2e_test.go +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright 2020 ChainSafe Systems -// SPDX-License-Identifier: LGPL-3.0-only - -package e2e - -import ( - "fmt" - "math/big" - "testing" - - ethChain "github.com/ChainSafe/ChainBridge/chains/ethereum" - subChain "github.com/ChainSafe/ChainBridge/chains/substrate" - "github.com/ChainSafe/ChainBridge/core" - eth "github.com/ChainSafe/ChainBridge/e2e/ethereum" - sub "github.com/ChainSafe/ChainBridge/e2e/substrate" - msg "github.com/ChainSafe/ChainBridge/message" - "github.com/ChainSafe/ChainBridge/shared" - ethutils "github.com/ChainSafe/ChainBridge/shared/ethereum" - ethtest "github.com/ChainSafe/ChainBridge/shared/ethereum/testing" - subutils "github.com/ChainSafe/ChainBridge/shared/substrate" - subtest "github.com/ChainSafe/ChainBridge/shared/substrate/testing" - log "github.com/ChainSafe/log15" - "github.com/centrifuge/go-substrate-rpc-client/types" - "github.com/ethereum/go-ethereum/common" -) - -const EthAChainId = msg.ChainId(0) -const SubChainId = msg.ChainId(1) -const EthBChainId = msg.ChainId(2) - -type testContext struct { - ethA *eth.TestContext - ethB *eth.TestContext - subClient *subutils.Client - - EthSubErc20ResourceId msg.ResourceId - EthEthErc20ResourceId msg.ResourceId - EthSubErc721ResourceId msg.ResourceId - EthEthErc721ResourceId msg.ResourceId - GenericHashResourceId msg.ResourceId - EthGenericResourceId msg.ResourceId -} - -func createAndStartBridge(t *testing.T, name string, contractsA, contractsB *ethutils.DeployedContracts) (*core.Core, log.Logger) { - // Create logger to write to a file, and store the log file name in global var - logger := log.Root().New() - sysErr := make(chan error) - ethACfg := eth.CreateConfig(t, name, EthAChainId, contractsA, eth.EthAEndpoint) - ethA, err := ethChain.InitializeChain(ethACfg, logger.New("relayer", name, "chain", "ethA"), sysErr) - if err != nil { - t.Fatal(err) - } - - subCfg := sub.CreateConfig(t, name, SubChainId) - subA, err := subChain.InitializeChain(subCfg, logger.New("relayer", name, "chain", "sub"), sysErr) - if err != nil { - t.Fatal(err) - } - - ethBCfg := eth.CreateConfig(t, name, EthBChainId, contractsB, eth.EthBEndpoint) - ethB, err := ethChain.InitializeChain(ethBCfg, logger.New("relayer", name, "chain", "ethB"), sysErr) - if err != nil { - t.Fatal(err) - } - - bridge := core.NewCore(sysErr) - bridge.AddChain(ethA) - bridge.AddChain(subA) - bridge.AddChain(ethB) - - err = ethA.Start() - if err != nil { - t.Fatal(err) - } - - err = subA.Start() - if err != nil { - t.Fatal(err) - } - - err = ethB.Start() - if err != nil { - t.Fatal(err) - } - - return bridge, logger -} - -func assertChanError(t *testing.T, errs <-chan error) { - select { - case err := <-errs: - t.Fatalf("BridgeA Fatal Error: %s", err) - default: - // Do nothing - fmt.Println("No errors here!") - return - } -} - -func setupFungibleTests(t *testing.T, ctx *testContext) { - mintAmount := big.NewInt(1000) - approveAmount := big.NewInt(500) - - // Deploy Sub<>Eth erc20 on ethA, register resource ID, add handler as minter - erc20ContractASub := ethtest.Erc20DeployMint(t, ctx.ethA.Client, mintAmount) - log.Info("Deployed erc20 contract", "address", erc20ContractASub) - ethtest.Erc20Approve(t, ctx.ethA.Client, erc20ContractASub, ctx.ethA.BaseContracts.ERC20HandlerAddress, approveAmount) - ethtest.Erc20AddMinter(t, ctx.ethA.Client, erc20ContractASub, ctx.ethA.BaseContracts.ERC20HandlerAddress) - ethtest.RegisterResource(t, ctx.ethA.Client, ctx.ethA.BaseContracts.BridgeAddress, ctx.ethA.BaseContracts.ERC20HandlerAddress, ctx.EthSubErc20ResourceId, erc20ContractASub) - ethtest.SetBurnable(t, ctx.ethA.Client, ctx.ethA.BaseContracts.BridgeAddress, ctx.ethA.BaseContracts.ERC20HandlerAddress, erc20ContractASub) - - // Deploy Eth<>Eth erc20 on ethA, register resource ID, add handler as minter - erc20ContractAEth := ethtest.Erc20DeployMint(t, ctx.ethA.Client, mintAmount) - log.Info("Deployed erc20 contract", "address", erc20ContractAEth) - ethtest.Erc20Approve(t, ctx.ethA.Client, erc20ContractAEth, ctx.ethA.BaseContracts.ERC20HandlerAddress, approveAmount) - ethtest.Erc20AddMinter(t, ctx.ethA.Client, erc20ContractAEth, ctx.ethA.BaseContracts.ERC20HandlerAddress) - ethErc20ResourceId := msg.ResourceIdFromSlice(append(common.LeftPadBytes(erc20ContractAEth.Bytes(), 31), 0)) - ethtest.RegisterResource(t, ctx.ethA.Client, ctx.ethA.BaseContracts.BridgeAddress, ctx.ethA.BaseContracts.ERC20HandlerAddress, ethErc20ResourceId, erc20ContractAEth) - ethtest.SetBurnable(t, ctx.ethA.Client, ctx.ethA.BaseContracts.BridgeAddress, ctx.ethA.BaseContracts.ERC20HandlerAddress, erc20ContractAEth) - - // Deploy Eth<>Eth erc20 on ethB, add handler as minter - erc20ContractBEth := ethtest.Erc20DeployMint(t, ctx.ethB.Client, mintAmount) - log.Info("Deployed erc20 contract", "address", erc20ContractBEth) - ethtest.Erc20AddMinter(t, ctx.ethB.Client, erc20ContractBEth, ctx.ethB.BaseContracts.ERC20HandlerAddress) - ethtest.Erc20Approve(t, ctx.ethB.Client, erc20ContractBEth, ctx.ethB.BaseContracts.ERC20HandlerAddress, approveAmount) - ethtest.RegisterResource(t, ctx.ethB.Client, ctx.ethB.BaseContracts.BridgeAddress, ctx.ethB.BaseContracts.ERC20HandlerAddress, ethErc20ResourceId, erc20ContractBEth) - ethtest.SetBurnable(t, ctx.ethB.Client, ctx.ethB.BaseContracts.BridgeAddress, ctx.ethB.BaseContracts.ERC20HandlerAddress, erc20ContractBEth) - - ctx.ethA.TestContracts.Erc20Sub = erc20ContractASub - ctx.ethA.TestContracts.Erc20Eth = erc20ContractAEth - ctx.ethB.TestContracts.Erc20Eth = erc20ContractBEth - ctx.EthEthErc20ResourceId = ethErc20ResourceId -} - -func setupNonFungibleTests(t *testing.T, ctx *testContext) { - - // Deploy Sub<>Eth erc721 on ethA, register resource ID, set burnable - erc721ContractASub := ethtest.Erc721Deploy(t, ctx.ethA.Client) - ethtest.Erc721AddMinter(t, ctx.ethA.Client, erc721ContractASub, ctx.ethA.BaseContracts.ERC721HandlerAddress) - ethtest.RegisterResource(t, ctx.ethA.Client, ctx.ethA.BaseContracts.BridgeAddress, ctx.ethA.BaseContracts.ERC721HandlerAddress, ctx.EthSubErc721ResourceId, erc721ContractASub) - ethtest.SetBurnable(t, ctx.ethA.Client, ctx.ethA.BaseContracts.BridgeAddress, ctx.ethA.BaseContracts.ERC721HandlerAddress, erc721ContractASub) - - // Deploy Eth<>Eth erc721 on ethA, register resource ID, set burnable - erc721ContractAEth := ethtest.Erc721Deploy(t, ctx.ethA.Client) - ethtest.Erc721AddMinter(t, ctx.ethA.Client, erc721ContractAEth, ctx.ethA.BaseContracts.ERC721HandlerAddress) - ethErc721ResourceId := msg.ResourceIdFromSlice(append(common.LeftPadBytes(erc721ContractAEth.Bytes(), 31), byte(EthAChainId))) - ethtest.RegisterResource(t, ctx.ethA.Client, ctx.ethA.BaseContracts.BridgeAddress, ctx.ethA.BaseContracts.ERC721HandlerAddress, ethErc721ResourceId, erc721ContractAEth) - ethtest.SetBurnable(t, ctx.ethA.Client, ctx.ethA.BaseContracts.BridgeAddress, ctx.ethA.BaseContracts.ERC721HandlerAddress, erc721ContractASub) - - // Deploy Eth<>Eth erc721 on ethB, register resource ID, set burnable - erc721ContractBEth := ethtest.Erc721Deploy(t, ctx.ethB.Client) - ethtest.Erc721AddMinter(t, ctx.ethB.Client, erc721ContractBEth, ctx.ethB.BaseContracts.ERC721HandlerAddress) - ethtest.RegisterResource(t, ctx.ethB.Client, ctx.ethB.BaseContracts.BridgeAddress, ctx.ethB.BaseContracts.ERC721HandlerAddress, ethErc721ResourceId, erc721ContractBEth) - ethtest.SetBurnable(t, ctx.ethB.Client, ctx.ethB.BaseContracts.BridgeAddress, ctx.ethB.BaseContracts.ERC721HandlerAddress, erc721ContractBEth) - - ctx.ethA.TestContracts.Erc721Sub = erc721ContractASub - ctx.ethA.TestContracts.Erc721Eth = erc721ContractAEth - ctx.ethB.TestContracts.Erc721Eth = erc721ContractBEth - ctx.EthEthErc721ResourceId = ethErc721ResourceId -} - -func setupGenericTests(t *testing.T, ctx *testContext) { - // Deploy asset store for sub->eth on ethA, register resource ID - assetStoreContractASub := ethtest.DeployAssetStore(t, ctx.ethA.Client) - ethtest.RegisterGenericResource(t, ctx.ethA.Client, ctx.ethA.BaseContracts.BridgeAddress, ctx.ethA.BaseContracts.GenericHandlerAddress, ctx.GenericHashResourceId, assetStoreContractASub, [4]byte{}, ethutils.StoreFunctionSig) - - // Deploy asset store for eth->eth on ethB, register resource ID - assetStoreContractBEth := ethtest.DeployAssetStore(t, ctx.ethB.Client) - ethGenericResourceId := msg.ResourceIdFromSlice(append(common.LeftPadBytes(assetStoreContractBEth.Bytes(), 31), byte(EthBChainId))) - ethtest.RegisterGenericResource(t, ctx.ethB.Client, ctx.ethB.BaseContracts.BridgeAddress, ctx.ethB.BaseContracts.GenericHandlerAddress, ethGenericResourceId, assetStoreContractBEth, [4]byte{}, ethutils.StoreFunctionSig) - // Register resource on ethA as well for deposit, address used could be anything - ethtest.RegisterGenericResource(t, ctx.ethA.Client, ctx.ethA.BaseContracts.BridgeAddress, ctx.ethA.BaseContracts.GenericHandlerAddress, ethGenericResourceId, assetStoreContractBEth, [4]byte{}, ethutils.StoreFunctionSig) - - ctx.ethA.TestContracts.AssetStoreSub = assetStoreContractASub - ctx.ethB.TestContracts.AssetStoreEth = assetStoreContractBEth - ctx.EthGenericResourceId = ethGenericResourceId -} - -// This tests three relayers connected to three chains (2 ethereum, 1 substrate). -// -// EthA: -// - Native erc20 token -// Eth B: -// - Synthetic erc20 token -// Substrate: -// - Synthetic token (native to chain) -// -func Test_ThreeRelayers(t *testing.T) { - shared.SetLogger(log.LvlTrace) - threshold := 3 - - // Setup test client connections for each chain - ethClientA := ethtest.NewClient(t, eth.EthAEndpoint, eth.AliceKp) - ethClientB := ethtest.NewClient(t, eth.EthBEndpoint, eth.AliceKp) - subClient := subtest.CreateClient(t, sub.AliceKp.AsKeyringPair(), sub.TestSubEndpoint) - - // First lookup the substrate resource IDs - var rawRId types.Bytes32 - subtest.QueryConst(t, subClient, "Example", "NativeTokenId", &rawRId) - subErc20ResourceId := msg.ResourceIdFromSlice(rawRId[:]) - subtest.QueryConst(t, subClient, "Example", "Erc721Id", &rawRId) - subErc721ResourceId := msg.ResourceIdFromSlice(rawRId[:]) - subtest.QueryConst(t, subClient, "Example", "HashId", &rawRId) - genericHashResourceId := msg.ResourceIdFromSlice(rawRId[:]) - - // Base setup for ethA - contractsA := eth.DeployTestContracts(t, ethClientA, eth.EthAEndpoint, EthAChainId, big.NewInt(int64(threshold))) - // Base setup for ethB ERC20 - handler can mint - contractsB := eth.DeployTestContracts(t, ethClientB, eth.EthBEndpoint, EthBChainId, big.NewInt(int64(threshold))) - - // Create the initial context, added to in setup functions - ctx := &testContext{ - ethA: ð.TestContext{ - BaseContracts: contractsA, - TestContracts: eth.TestContracts{}, - Client: ethClientA, - }, - ethB: ð.TestContext{ - BaseContracts: contractsB, - TestContracts: eth.TestContracts{}, - Client: ethClientB, - }, - subClient: subClient, - EthSubErc20ResourceId: subErc20ResourceId, - EthSubErc721ResourceId: subErc721ResourceId, - GenericHashResourceId: genericHashResourceId, - } - - setupFungibleTests(t, ctx) - setupNonFungibleTests(t, ctx) - setupGenericTests(t, ctx) - - // Setup substrate client, register resource, add relayers - resources := map[msg.ResourceId]subutils.Method{ - subErc20ResourceId: subutils.ExampleTransferMethod, - subErc721ResourceId: subutils.ExampleMintErc721Method, - genericHashResourceId: subutils.ExampleRemarkMethod, - } - subtest.EnsureInitializedChain(t, subClient, sub.RelayerSet, []msg.ChainId{EthAChainId}, resources, uint32(threshold)) - - // Create and start three bridges with both chains - bridgeA, _ := createAndStartBridge(t, "bob", contractsA, contractsB) - bridgeB, _ := createAndStartBridge(t, "charlie", contractsA, contractsB) - bridgeC, _ := createAndStartBridge(t, "dave", contractsA, contractsB) - - assertChanError(t, bridgeA.Errors()) - assertChanError(t, bridgeB.Errors()) - assertChanError(t, bridgeC.Errors()) -} diff --git a/e2e/ethereum/ethereum.go b/e2e/ethereum/ethereum.go index b75a1aa6a..0b19c5322 100644 --- a/e2e/ethereum/ethereum.go +++ b/e2e/ethereum/ethereum.go @@ -5,7 +5,6 @@ package ethereum import ( "context" - "encoding/json" "fmt" "math/big" "os" @@ -57,7 +56,7 @@ type TestContracts struct { } func CreateConfig(t *testing.T, key string, chain msg.ChainId, contracts *utils.DeployedContracts, endpoint string) *core.ChainConfig { - c := &core.ChainConfig{ + return &core.ChainConfig{ Name: fmt.Sprintf("ethereum(%s,%d)", key, chain), Id: chain, Endpoint: endpoint, @@ -73,18 +72,6 @@ func CreateConfig(t *testing.T, key string, chain msg.ChainId, contracts *utils. "genericHandler": contracts.GenericHandlerAddress.String(), }, } - - json, err := json.Marshal(c) - if err != nil { - t.Fatal(err) - } - - fmt.Println("======================== Ethereum Chain Config ========================") - fmt.Println(string(json)) - fmt.Println("=======================================================================") - - return c - } func DeployTestContracts(t *testing.T, client *utils.Client, endpoint string, id msg.ChainId, threshold *big.Int) *utils.DeployedContracts { diff --git a/e2e/substrate/substrate.go b/e2e/substrate/substrate.go index 767cbaea3..cdf8411df 100644 --- a/e2e/substrate/substrate.go +++ b/e2e/substrate/substrate.go @@ -4,7 +4,6 @@ package substrate import ( - "encoding/json" "fmt" "os" "testing" @@ -37,7 +36,7 @@ var RelayerSet = []types.AccountID{ } func CreateConfig(t *testing.T, key string, chain msg.ChainId) *core.ChainConfig { - c := &core.ChainConfig{ + return &core.ChainConfig{ Name: fmt.Sprintf("substrate(%s)", key), Id: chain, Endpoint: TestSubEndpoint, @@ -48,17 +47,6 @@ func CreateConfig(t *testing.T, key string, chain msg.ChainId) *core.ChainConfig BlockstorePath: os.TempDir(), Opts: map[string]string{}, } - - json, err := json.Marshal(c) - if err != nil { - t.Fatal(err) - } - - fmt.Println("======================== Substrate Chain Config ========================") - fmt.Println(string(json)) - fmt.Println("========================================================================") - - return c } func WaitForProposalSuccessOrFail(t *testing.T, client *utils.Client, nonce types.U64, chain types.U8) { From a601200939080de77c8b92ac4327cd9dfadf53e5 Mon Sep 17 00:00:00 2001 From: Gregory Markou Date: Tue, 25 Aug 2020 14:19:06 +0300 Subject: [PATCH 14/21] switch registry to array --- core/core.go | 22 ++++++---------------- monitor/health/http.go | 3 +-- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/core/core.go b/core/core.go index b13b0a6fe..937c79cac 100644 --- a/core/core.go +++ b/core/core.go @@ -9,14 +9,13 @@ import ( "os/signal" "syscall" - msg "github.com/ChainSafe/ChainBridge/message" "github.com/ChainSafe/ChainBridge/router" "github.com/ChainSafe/log15" ) type Core struct { - registry map[msg.ChainId]Chain + Registry []Chain route *router.Router log log15.Logger sysErr <-chan error @@ -24,22 +23,22 @@ type Core struct { func NewCore(sysErr <-chan error) *Core { return &Core{ - registry: make(map[msg.ChainId]Chain), + Registry: make([]Chain, 0), route: router.NewRouter(log15.New("system", "router")), log: log15.New("system", "core"), sysErr: sysErr, } } -// AddChain registers the chain in the registry and calls Chain.SetRouter() +// AddChain registers the chain in the Registry and calls Chain.SetRouter() func (c *Core) AddChain(chain Chain) { - c.registry[chain.Id()] = chain + c.Registry = append(c.Registry, chain) chain.SetRouter(c.route) } // Start will call all registered chains' Start methods and block forever (or until signal is received) func (c *Core) Start() { - for _, chain := range c.registry { + for _, chain := range c.Registry { err := chain.Start() if err != nil { c.log.Error( @@ -65,7 +64,7 @@ func (c *Core) Start() { } // Signal chains to shutdown - for _, chain := range c.registry { + for _, chain := range c.Registry { chain.Stop() } } @@ -73,12 +72,3 @@ func (c *Core) Start() { func (c *Core) Errors() <-chan error { return c.sysErr } - -// GetChains returns all the chains found in the registry -func (c *Core) GetChains() []Chain { - chains := make([]Chain, 0, len(c.registry)) - for k := range c.registry { - chains = append(chains, c.registry[k]) - } - return chains -} diff --git a/monitor/health/http.go b/monitor/health/http.go index 5ee9f6d5e..bad34ac60 100644 --- a/monitor/health/http.go +++ b/monitor/health/http.go @@ -72,7 +72,7 @@ func (s httpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") // Grab all chains - chains := s.core.GetChains() + chains := s.core.Registry requestTime := time.Now() // Iterate through their block heads and update the cache accordingly @@ -89,7 +89,6 @@ func (s httpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { height: latestHeight, lastUpdated: requestTime, } - return } else { if int(timeDiff.Seconds()) > s.timeDelay { // Error if we exceeded the time limit From d751a7ed0b72f4d9ef71793087be53543463a7cf Mon Sep 17 00:00:00 2001 From: Gregory Markou Date: Tue, 25 Aug 2020 14:37:19 +0300 Subject: [PATCH 15/21] pull timestamp from listener --- chains/ethereum/chain.go | 2 +- chains/ethereum/listener.go | 8 ++++++-- chains/substrate/chain.go | 4 +--- chains/substrate/listener.go | 8 ++++++-- core/chain.go | 8 +++++++- monitor/health/http.go | 29 ++++++++++++++--------------- 6 files changed, 35 insertions(+), 24 deletions(-) diff --git a/chains/ethereum/chain.go b/chains/ethereum/chain.go index da24b8909..1e554e5cd 100644 --- a/chains/ethereum/chain.go +++ b/chains/ethereum/chain.go @@ -205,7 +205,7 @@ func (c *Chain) Name() string { return c.cfg.Name } -func (c *Chain) GetLatestBlock() *big.Int { +func (c *Chain) GetLatestBlock() core.LatestBlock { return c.listener.latestBlock } diff --git a/chains/ethereum/listener.go b/chains/ethereum/listener.go index 1f8480d3a..886e7d1b3 100644 --- a/chains/ethereum/listener.go +++ b/chains/ethereum/listener.go @@ -16,6 +16,7 @@ import ( "github.com/ChainSafe/ChainBridge/bindings/GenericHandler" "github.com/ChainSafe/ChainBridge/blockstore" "github.com/ChainSafe/ChainBridge/chains" + "github.com/ChainSafe/ChainBridge/core" msg "github.com/ChainSafe/ChainBridge/message" utils "github.com/ChainSafe/ChainBridge/shared/ethereum" "github.com/ChainSafe/log15" @@ -41,7 +42,7 @@ type listener struct { blockstore blockstore.Blockstorer stop <-chan int sysErr chan<- error // Reports fatal error to core - latestBlock *big.Int + latestBlock core.LatestBlock } // NewListener creates and returns a listener @@ -133,7 +134,10 @@ func (l *listener) pollBlocks() error { // Goto next block and reset retry counter currentBlock.Add(currentBlock, big.NewInt(1)) - l.latestBlock = currentBlock + l.latestBlock = core.LatestBlock{ + Height: latestBlock, + Timestamp: time.Now(), + } retry = BlockRetryLimit } } diff --git a/chains/substrate/chain.go b/chains/substrate/chain.go index f64a0cba0..3dc98cf2a 100644 --- a/chains/substrate/chain.go +++ b/chains/substrate/chain.go @@ -24,8 +24,6 @@ As the writer receives messages from the router, it constructs proposals. If a p package substrate import ( - "math/big" - "github.com/ChainSafe/ChainBridge/blockstore" "github.com/ChainSafe/ChainBridge/core" "github.com/ChainSafe/ChainBridge/crypto/sr25519" @@ -134,7 +132,7 @@ func (c *Chain) SetRouter(r *router.Router) { c.listener.setRouter(r) } -func (c *Chain) GetLatestBlock() *big.Int { +func (c *Chain) GetLatestBlock() core.LatestBlock { return c.listener.latestBlock } diff --git a/chains/substrate/listener.go b/chains/substrate/listener.go index 16de569ad..862e0c82e 100644 --- a/chains/substrate/listener.go +++ b/chains/substrate/listener.go @@ -11,6 +11,7 @@ import ( "github.com/ChainSafe/ChainBridge/blockstore" "github.com/ChainSafe/ChainBridge/chains" + "github.com/ChainSafe/ChainBridge/core" msg "github.com/ChainSafe/ChainBridge/message" utils "github.com/ChainSafe/ChainBridge/shared/substrate" "github.com/ChainSafe/log15" @@ -28,7 +29,7 @@ type listener struct { log log15.Logger stop <-chan int sysErr chan<- error - latestBlock *big.Int + latestBlock core.LatestBlock } // Frequency of polling for a new block @@ -160,7 +161,10 @@ func (l *listener) pollBlocks() error { } currentBlock++ - l.latestBlock = big.NewInt(0).SetUint64(currentBlock) + l.latestBlock = core.LatestBlock{ + Height: big.NewInt(0).SetUint64(currentBlock), + Timestamp: time.Now(), + } retry = BlockRetryLimit } } diff --git a/core/chain.go b/core/chain.go index f2b9d9271..d4912684a 100644 --- a/core/chain.go +++ b/core/chain.go @@ -5,6 +5,7 @@ package core import ( "math/big" + "time" msg "github.com/ChainSafe/ChainBridge/message" "github.com/ChainSafe/ChainBridge/router" @@ -15,10 +16,15 @@ type Chain interface { SetRouter(*router.Router) Id() msg.ChainId Name() string - GetLatestBlock() *big.Int + GetLatestBlock() LatestBlock Stop() } +type LatestBlock struct { + Height *big.Int + Timestamp time.Time +} + type ChainConfig struct { Name string // Human-readable chain name Id msg.ChainId // ChainID diff --git a/monitor/health/http.go b/monitor/health/http.go index bad34ac60..8dc192caf 100644 --- a/monitor/health/http.go +++ b/monitor/health/http.go @@ -50,7 +50,7 @@ func newhttpMetricServer(opts httpMetricOptions) *httpMetricServer { // Start starts the http metrics server func (s httpMetricServer) Start() { - log.Info("Metrics server started", "port", s.port) + log.Info("Health status server started listening on", "port", s.port) // Setup routes http.HandleFunc("/health", s.healthStatus) @@ -59,9 +59,9 @@ func (s httpMetricServer) Start() { err := http.ListenAndServe(":"+strconv.Itoa(s.port), nil) if err == http.ErrServerClosed { - log.Info("Server is shutting down", err) + log.Info("Health status server is shutting down", err) } else { - log.Error("Shutting down, server error: ", err) + log.Error("Health status server shutting down, server error: ", err) } } @@ -73,45 +73,44 @@ func (s httpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { // Grab all chains chains := s.core.Registry - requestTime := time.Now() // Iterate through their block heads and update the cache accordingly for _, chain := range chains { - latestHeight := chain.GetLatestBlock() + latestBlock := chain.GetLatestBlock() // Get old blockheight if prevHeight, ok := s.blockheights[chain.Id()]; ok { // Note The listener performing the polling should already be accounting for the block delay // TODO Account for timestamps - timeDiff := requestTime.Sub(prevHeight.lastUpdated) - if latestHeight.Cmp(prevHeight.height) >= 0 && int(timeDiff.Seconds()) < s.timeDelay { + timeDiff := latestBlock.Timestamp.Sub(prevHeight.lastUpdated) + if latestBlock.Height.Cmp(prevHeight.height) >= 0 && int(timeDiff.Seconds()) < s.timeDelay { s.blockheights[chain.Id()] = blockHeightInfo{ - height: latestHeight, - lastUpdated: requestTime, + height: latestBlock.Height, + lastUpdated: latestBlock.Timestamp, } } else { if int(timeDiff.Seconds()) > s.timeDelay { // Error if we exceeded the time limit response := &httpResponse{ Data: "", - Error: fmt.Sprintf("%s%s%s%s", "Chain height hasn't changed in: ", timeDiff, " Current height: ", latestHeight), + Error: fmt.Sprintf("%s%s%s%s", "Chain height hasn't changed in: ", timeDiff, " Current height: ", latestBlock.Height), } w.WriteHeader(http.StatusInternalServerError) err := json.NewEncoder(w).Encode(response) if err != nil { - log.Error("Failed to write, chain height hasn't changed in: %d seconds, Current height: %d", timeDiff.Seconds(), latestHeight, err) + log.Error("Failed to write, chain height hasn't changed in: %d seconds, Current height: %d", timeDiff.Seconds(), latestBlock.Height, err) } return } else { // Error for having a smaller blockheight than previous response := &httpResponse{ Data: "", - Error: fmt.Sprintf("%s%s%s%s%s", "latestHeight is <= previousHeight ", "previousHeight: ", prevHeight.height, "latestHeight: ", latestHeight), + Error: fmt.Sprintf("%s%s%s%s%s", "latestBlock is <= previousHeight ", "previousHeight: ", prevHeight.height, "latestBlock: ", latestBlock.Height), } w.WriteHeader(http.StatusInternalServerError) err := json.NewEncoder(w).Encode(response) if err != nil { - log.Error("Failed to write, latest height less than previous height, latest height: %d, previous height: %d", latestHeight, prevHeight.height, err) + log.Error("Failed to write, latest height less than previous height, latest height: %d, previous height: %d", latestBlock.Height, prevHeight.height, err) } return } @@ -120,8 +119,8 @@ func (s httpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { // Note: Could be edge case where chain never started, perhaps push initialization to different step // First time we've received a block for this chain s.blockheights[chain.Id()] = blockHeightInfo{ - height: latestHeight, - lastUpdated: requestTime, + height: latestBlock.Height, + lastUpdated: latestBlock.Timestamp, } } } From 3861489cc5d2c6a9e84438197d61a2d031a67384 Mon Sep 17 00:00:00 2001 From: Gregory Markou Date: Tue, 25 Aug 2020 14:43:03 +0300 Subject: [PATCH 16/21] reset test files --- e2e/e2e_test.go | 6 +++--- e2e/ethereum/ethereum.go | 2 +- e2e/substrate/substrate.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index feaea35c5..bd99dccf5 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -70,19 +70,19 @@ func createAndStartBridge(t *testing.T, name string, contractsA, contractsB *eth // Create logger to write to a file, and store the log file name in global var logger := log.Root().New() sysErr := make(chan error) - ethACfg := eth.CreateConfig(t, name, EthAChainId, contractsA, eth.EthAEndpoint) + ethACfg := eth.CreateConfig(name, EthAChainId, contractsA, eth.EthAEndpoint) ethA, err := ethChain.InitializeChain(ethACfg, logger.New("relayer", name, "chain", "ethA"), sysErr) if err != nil { t.Fatal(err) } - subCfg := sub.CreateConfig(t, name, SubChainId) + subCfg := sub.CreateConfig(name, SubChainId) subA, err := subChain.InitializeChain(subCfg, logger.New("relayer", name, "chain", "sub"), sysErr) if err != nil { t.Fatal(err) } - ethBCfg := eth.CreateConfig(t, name, EthBChainId, contractsB, eth.EthBEndpoint) + ethBCfg := eth.CreateConfig(name, EthBChainId, contractsB, eth.EthBEndpoint) ethB, err := ethChain.InitializeChain(ethBCfg, logger.New("relayer", name, "chain", "ethB"), sysErr) if err != nil { t.Fatal(err) diff --git a/e2e/ethereum/ethereum.go b/e2e/ethereum/ethereum.go index 0b19c5322..9456fda97 100644 --- a/e2e/ethereum/ethereum.go +++ b/e2e/ethereum/ethereum.go @@ -55,7 +55,7 @@ type TestContracts struct { AssetStoreEth common.Address // Contract configured for eth to eth generic transfer } -func CreateConfig(t *testing.T, key string, chain msg.ChainId, contracts *utils.DeployedContracts, endpoint string) *core.ChainConfig { +func CreateConfig(key string, chain msg.ChainId, contracts *utils.DeployedContracts, endpoint string) *core.ChainConfig { return &core.ChainConfig{ Name: fmt.Sprintf("ethereum(%s,%d)", key, chain), Id: chain, diff --git a/e2e/substrate/substrate.go b/e2e/substrate/substrate.go index cdf8411df..bb6e54c3a 100644 --- a/e2e/substrate/substrate.go +++ b/e2e/substrate/substrate.go @@ -35,7 +35,7 @@ var RelayerSet = []types.AccountID{ types.NewAccountID(DaveKp.AsKeyringPair().PublicKey), } -func CreateConfig(t *testing.T, key string, chain msg.ChainId) *core.ChainConfig { +func CreateConfig(key string, chain msg.ChainId) *core.ChainConfig { return &core.ChainConfig{ Name: fmt.Sprintf("substrate(%s)", key), Id: chain, From c545ba00a344cc8f233652aacf7f0d82e106e3b0 Mon Sep 17 00:00:00 2001 From: Gregory Markou Date: Tue, 25 Aug 2020 19:15:12 +0300 Subject: [PATCH 17/21] update code --- chains/ethereum/listener.go | 19 +++++++++---------- chains/substrate/listener.go | 7 +++---- core/chain.go | 1 + test-config.json | 21 +++++++++++++++++++++ 4 files changed, 34 insertions(+), 14 deletions(-) create mode 100644 test-config.json diff --git a/chains/ethereum/listener.go b/chains/ethereum/listener.go index 886e7d1b3..b949b2d51 100644 --- a/chains/ethereum/listener.go +++ b/chains/ethereum/listener.go @@ -48,12 +48,13 @@ type listener struct { // NewListener creates and returns a listener func NewListener(conn Connection, cfg *Config, log log15.Logger, bs blockstore.Blockstorer, stop <-chan int, sysErr chan<- error) *listener { return &listener{ - cfg: *cfg, - conn: conn, - log: log, - blockstore: bs, - stop: stop, - sysErr: sysErr, + cfg: *cfg, + conn: conn, + log: log, + blockstore: bs, + stop: stop, + sysErr: sysErr, + latestBlock: core.LatestBlock{}, } } @@ -134,10 +135,8 @@ func (l *listener) pollBlocks() error { // Goto next block and reset retry counter currentBlock.Add(currentBlock, big.NewInt(1)) - l.latestBlock = core.LatestBlock{ - Height: latestBlock, - Timestamp: time.Now(), - } + l.latestBlock.Height = latestBlock + l.latestBlock.Timestamp = time.Now() retry = BlockRetryLimit } } diff --git a/chains/substrate/listener.go b/chains/substrate/listener.go index 862e0c82e..6018f62b1 100644 --- a/chains/substrate/listener.go +++ b/chains/substrate/listener.go @@ -47,6 +47,7 @@ func NewListener(conn *Connection, name string, id msg.ChainId, startBlock uint6 log: log, stop: stop, sysErr: sysErr, + latestBlock: core.LatestBlock{}, } } @@ -161,10 +162,8 @@ func (l *listener) pollBlocks() error { } currentBlock++ - l.latestBlock = core.LatestBlock{ - Height: big.NewInt(0).SetUint64(currentBlock), - Timestamp: time.Now(), - } + l.latestBlock.Height = big.NewInt(0).SetUint64(currentBlock) + l.latestBlock.Timestamp = time.Now() retry = BlockRetryLimit } } diff --git a/core/chain.go b/core/chain.go index d4912684a..15f387f47 100644 --- a/core/chain.go +++ b/core/chain.go @@ -6,6 +6,7 @@ package core import ( "math/big" "time" + "sync" msg "github.com/ChainSafe/ChainBridge/message" "github.com/ChainSafe/ChainBridge/router" diff --git a/test-config.json b/test-config.json new file mode 100644 index 000000000..e5e8cb372 --- /dev/null +++ b/test-config.json @@ -0,0 +1,21 @@ +{ + "Chains": [ + { + "name": "goerli", + "type": "ethereum", + "id": "0", + "endpoint": "ws://localhost:8545", + "from": "0x02642514dbebb135043cdbae4599a04a2ea84c9bcd701575d77d1a4c7cb2b977e0", + "opts": { + "bridge": "0x62877dDCd49aD22f5eDfc6ac108e9a4b5D2bD88B", + "erc20Handler": "0x3167776db165D8eA0f51790CA2bbf44Db5105ADF", + "erc721Handler": "0x3f709398808af36ADBA86ACC617FeB7F5B7B193E", + "genericHandler": "0x2B6Ab4b880A45a07d83Cf4d664Df4Ab85705Bc07", + "gasLimit": "1000000", + "maxGasPrice": "20000000", + "startBlock": "0", + "http": "false" + } + } + ] +} \ No newline at end of file From f211bde94f4e13a1d48c72d92b8e63a97604dc44 Mon Sep 17 00:00:00 2001 From: David Ansermino Date: Wed, 26 Aug 2020 10:09:06 -0400 Subject: [PATCH 18/21] Cleanup health server. Adds chain stats to response --- README.md | 9 ++- chains/ethereum/chain.go | 3 +- chains/ethereum/listener.go | 8 +- chains/substrate/chain.go | 3 +- chains/substrate/listener.go | 8 +- core/chain.go | 12 +-- metrics/health/health.go | 142 +++++++++++++++++++++++++++++++++++ metrics/types/types.go | 11 +++ monitor/health/health.go | 24 ------ monitor/health/http.go | 136 --------------------------------- 10 files changed, 174 insertions(+), 182 deletions(-) create mode 100644 metrics/health/health.go create mode 100644 metrics/types/types.go delete mode 100644 monitor/health/health.go delete mode 100644 monitor/health/http.go diff --git a/README.md b/README.md index cb99f3d17..70776c630 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ - [Installation](#installation) - [Configuration](#configuration) -- [Running](#running) - [Chain Implementations](#chain-implementations) - [Testing](#testing) - [Simulations](#simulations) @@ -97,6 +96,12 @@ To import private keys as keystores, use `chainbridge account import --privateKe For testing purposes, chainbridge provides 5 test keys. The can be used with `--testkey `, where `name` is one of `Alice`, `Bob`, `Charlie`, `Dave`, or `Eve`. +## Metrics + +A basic health status check can be enabled with the `--metrics` flag (default port `8001`, use `--metricsPort` to specify). + +The endpoint `/health` will return the current block height and a timestamp of when it was processed. If the timestamp is at least 120 seconds old an error will be returned. + # Chain Implementations - Ethereum (Solidity): [chainbridge-solidity](https://github.com/ChainSafe/chainbridge-solidity) @@ -109,7 +114,7 @@ For testing purposes, chainbridge provides 5 test keys. The can be used with `-- A substrate pallet that can be integrated into a chain, as well as an example pallet to demonstrate chain integration. -# MKdocs +# Docs MKdocs will generate static HTML files for Chainsafe markdown files located in `Chainbridge/docs/` diff --git a/chains/ethereum/chain.go b/chains/ethereum/chain.go index 1e554e5cd..8a7bf9862 100644 --- a/chains/ethereum/chain.go +++ b/chains/ethereum/chain.go @@ -34,6 +34,7 @@ import ( "github.com/ChainSafe/ChainBridge/crypto/secp256k1" "github.com/ChainSafe/ChainBridge/keystore" msg "github.com/ChainSafe/ChainBridge/message" + metrics "github.com/ChainSafe/ChainBridge/metrics/types" "github.com/ChainSafe/ChainBridge/router" "github.com/ChainSafe/log15" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -205,7 +206,7 @@ func (c *Chain) Name() string { return c.cfg.Name } -func (c *Chain) GetLatestBlock() core.LatestBlock { +func (c *Chain) LatestBlock() metrics.LatestBlock { return c.listener.latestBlock } diff --git a/chains/ethereum/listener.go b/chains/ethereum/listener.go index b949b2d51..2506686af 100644 --- a/chains/ethereum/listener.go +++ b/chains/ethereum/listener.go @@ -16,8 +16,8 @@ import ( "github.com/ChainSafe/ChainBridge/bindings/GenericHandler" "github.com/ChainSafe/ChainBridge/blockstore" "github.com/ChainSafe/ChainBridge/chains" - "github.com/ChainSafe/ChainBridge/core" msg "github.com/ChainSafe/ChainBridge/message" + metrics "github.com/ChainSafe/ChainBridge/metrics/types" utils "github.com/ChainSafe/ChainBridge/shared/ethereum" "github.com/ChainSafe/log15" eth "github.com/ethereum/go-ethereum" @@ -42,7 +42,7 @@ type listener struct { blockstore blockstore.Blockstorer stop <-chan int sysErr chan<- error // Reports fatal error to core - latestBlock core.LatestBlock + latestBlock metrics.LatestBlock } // NewListener creates and returns a listener @@ -54,7 +54,7 @@ func NewListener(conn Connection, cfg *Config, log log15.Logger, bs blockstore.B blockstore: bs, stop: stop, sysErr: sysErr, - latestBlock: core.LatestBlock{}, + latestBlock: metrics.LatestBlock{LastUpdated: time.Now()}, } } @@ -136,7 +136,7 @@ func (l *listener) pollBlocks() error { // Goto next block and reset retry counter currentBlock.Add(currentBlock, big.NewInt(1)) l.latestBlock.Height = latestBlock - l.latestBlock.Timestamp = time.Now() + l.latestBlock.LastUpdated = time.Now() retry = BlockRetryLimit } } diff --git a/chains/substrate/chain.go b/chains/substrate/chain.go index 3dc98cf2a..a6f5c33c9 100644 --- a/chains/substrate/chain.go +++ b/chains/substrate/chain.go @@ -29,6 +29,7 @@ import ( "github.com/ChainSafe/ChainBridge/crypto/sr25519" "github.com/ChainSafe/ChainBridge/keystore" msg "github.com/ChainSafe/ChainBridge/message" + metrics "github.com/ChainSafe/ChainBridge/metrics/types" "github.com/ChainSafe/ChainBridge/router" "github.com/ChainSafe/log15" ) @@ -132,7 +133,7 @@ func (c *Chain) SetRouter(r *router.Router) { c.listener.setRouter(r) } -func (c *Chain) GetLatestBlock() core.LatestBlock { +func (c *Chain) LatestBlock() metrics.LatestBlock { return c.listener.latestBlock } diff --git a/chains/substrate/listener.go b/chains/substrate/listener.go index 6018f62b1..5ad1d9be0 100644 --- a/chains/substrate/listener.go +++ b/chains/substrate/listener.go @@ -11,8 +11,8 @@ import ( "github.com/ChainSafe/ChainBridge/blockstore" "github.com/ChainSafe/ChainBridge/chains" - "github.com/ChainSafe/ChainBridge/core" msg "github.com/ChainSafe/ChainBridge/message" + metrics "github.com/ChainSafe/ChainBridge/metrics/types" utils "github.com/ChainSafe/ChainBridge/shared/substrate" "github.com/ChainSafe/log15" "github.com/centrifuge/go-substrate-rpc-client/types" @@ -29,7 +29,7 @@ type listener struct { log log15.Logger stop <-chan int sysErr chan<- error - latestBlock core.LatestBlock + latestBlock metrics.LatestBlock } // Frequency of polling for a new block @@ -47,7 +47,7 @@ func NewListener(conn *Connection, name string, id msg.ChainId, startBlock uint6 log: log, stop: stop, sysErr: sysErr, - latestBlock: core.LatestBlock{}, + latestBlock: metrics.LatestBlock{LastUpdated: time.Now()}, } } @@ -163,7 +163,7 @@ func (l *listener) pollBlocks() error { currentBlock++ l.latestBlock.Height = big.NewInt(0).SetUint64(currentBlock) - l.latestBlock.Timestamp = time.Now() + l.latestBlock.LastUpdated = time.Now() retry = BlockRetryLimit } } diff --git a/core/chain.go b/core/chain.go index 15f387f47..62f45fd47 100644 --- a/core/chain.go +++ b/core/chain.go @@ -4,11 +4,8 @@ package core import ( - "math/big" - "time" - "sync" - msg "github.com/ChainSafe/ChainBridge/message" + metrics "github.com/ChainSafe/ChainBridge/metrics/types" "github.com/ChainSafe/ChainBridge/router" ) @@ -17,15 +14,10 @@ type Chain interface { SetRouter(*router.Router) Id() msg.ChainId Name() string - GetLatestBlock() LatestBlock + LatestBlock() metrics.LatestBlock Stop() } -type LatestBlock struct { - Height *big.Int - Timestamp time.Time -} - type ChainConfig struct { Name string // Human-readable chain name Id msg.ChainId // ChainID diff --git a/metrics/health/health.go b/metrics/health/health.go new file mode 100644 index 000000000..e1752baa7 --- /dev/null +++ b/metrics/health/health.go @@ -0,0 +1,142 @@ +// Copyright 2020 ChainSafe Systems +// SPDX-License-Identifier: LGPL-3.0-only + +package health + +import ( + "encoding/json" + "fmt" + "math/big" + "net/http" + "strconv" + "time" + + "github.com/ChainSafe/ChainBridge/core" + msg "github.com/ChainSafe/ChainBridge/message" + log "github.com/ChainSafe/log15" +) + +// After this duration with no changes a chain will return an error +const BlockTimeout = 20 + +type httpMetricServer struct { + port int + timeDelay int + chains []core.Chain + stats []ChainInfo +} + +type httpMetricOptions struct { + port int + timeDelay int + chains []core.Chain +} + +type httpResponse struct { + Chains []ChainInfo `json:"chains,omitempty"` + Error string `json:"error,omitempty"` +} + +type ChainInfo struct { + ChainId msg.ChainId `json:"chainId"` + Height *big.Int `json:"height"` + LastUpdated time.Time `json:"lastUpdated"` +} + +// Start spins up the metrics server +func Start(port int, chains []core.Chain) { + opts := httpMetricOptions{ + port: port, + timeDelay: BlockTimeout, + chains: chains, + } + httpServer := newhttpMetricServer(opts) + httpServer.Start() +} + +func newhttpMetricServer(opts httpMetricOptions) *httpMetricServer { + return &httpMetricServer{ + port: opts.port, + chains: opts.chains, + timeDelay: opts.timeDelay, + stats: make([]ChainInfo, len(opts.chains)), + } +} + +// Start starts the http metrics server +func (s httpMetricServer) Start() { + log.Info("Health status server started listening on", "port", s.port) + + // Setup routes + http.HandleFunc("/health", s.healthStatus) + + // Start http server + err := http.ListenAndServe(":"+strconv.Itoa(s.port), nil) + + if err == http.ErrServerClosed { + log.Info("Health status server is shutting down", err) + } else { + log.Error("Health status server shutting down, server error: ", err) + } +} + +// healthStatus is a catch-all update that grabs the latest updates on the running chains +// It assumes that the configuration was set correctly, therefore the relevant chains are +// only those that are in the core.Core registry. +func (s httpMetricServer) healthStatus(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/json") + + // Iterate through their block heads and update the cache accordingly + for i, chain := range s.chains { + current := chain.LatestBlock() + prev := s.stats[i] + if s.stats[i].LastUpdated.IsZero() { + // First time we've received a block for this chain + s.stats[chain.Id()] = ChainInfo{ + ChainId: chain.Id(), + Height: current.Height, + LastUpdated: current.LastUpdated, + } + } else { + now := time.Now() + timeDiff := now.Sub(prev.LastUpdated) + // If block has changed, update it + if current.Height.Cmp(prev.Height) == 1 { + s.stats[chain.Id()].LastUpdated = current.LastUpdated + s.stats[chain.Id()].Height = current.Height + } else if int(timeDiff.Seconds()) >= s.timeDelay { // Error if we exceeded the time limit + response := &httpResponse{ + Chains: []ChainInfo{}, + Error: fmt.Sprintf("chain %d height hasn't changed for %f seconds. Current Height: %s", prev.ChainId, timeDiff.Seconds(), current.Height), + } + w.WriteHeader(http.StatusInternalServerError) + err := json.NewEncoder(w).Encode(response) + if err != nil { + log.Error("Failed to write metrics", "err", err) + } + return + } else if current.Height != nil && prev.Height != nil && current.Height.Cmp(prev.Height) == -1 { // Error for having a smaller blockheight than previous + response := &httpResponse{ + Chains: []ChainInfo{}, + Error: fmt.Sprintf("unexpected block height. previous = %s current = %s", prev.Height, current.Height), + } + w.WriteHeader(http.StatusInternalServerError) + err := json.NewEncoder(w).Encode(response) + if err != nil { + log.Error("Failed to write metrics", "err", err) + } + return + } + } + } + + response := &httpResponse{ + Chains: s.stats, + Error: "", + } + w.WriteHeader(http.StatusOK) + err := json.NewEncoder(w).Encode(response) + if err != nil { + log.Error("Failed to serve metrics") + } +} diff --git a/metrics/types/types.go b/metrics/types/types.go new file mode 100644 index 000000000..b22cf7e46 --- /dev/null +++ b/metrics/types/types.go @@ -0,0 +1,11 @@ +package types + +import ( + "math/big" + "time" +) + +type LatestBlock struct { + Height *big.Int + LastUpdated time.Time +} diff --git a/monitor/health/health.go b/monitor/health/health.go deleted file mode 100644 index a6809a0cf..000000000 --- a/monitor/health/health.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2020 ChainSafe Systems -// SPDX-License-Identifier: LGPL-3.0-only - -package health - -import "github.com/ChainSafe/ChainBridge/core" - -// Start spins up the metrics server -func Start(core *core.Core) { - // TODO add to config file - httpPort := 8000 - blockTimeDelay := 120 - httpEnabled := true - - if httpEnabled { - opts := httpMetricOptions{ - port: httpPort, - timeDelay: blockTimeDelay, - core: core, - } - httpServer := newhttpMetricServer(opts) - httpServer.Start() - } -} diff --git a/monitor/health/http.go b/monitor/health/http.go deleted file mode 100644 index 8dc192caf..000000000 --- a/monitor/health/http.go +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2020 ChainSafe Systems -// SPDX-License-Identifier: LGPL-3.0-only - -package health - -import ( - "encoding/json" - "fmt" - "math/big" - "net/http" - "strconv" - "time" - - "github.com/ChainSafe/ChainBridge/core" - msg "github.com/ChainSafe/ChainBridge/message" - log "github.com/ChainSafe/log15" -) - -type httpMetricServer struct { - port int - timeDelay int - core *core.Core - blockheights map[msg.ChainId]blockHeightInfo -} - -type blockHeightInfo struct { - height *big.Int - lastUpdated time.Time -} - -type httpMetricOptions struct { - port int - timeDelay int - core *core.Core -} - -type httpResponse struct { - Data string - Error string -} - -func newhttpMetricServer(opts httpMetricOptions) *httpMetricServer { - return &httpMetricServer{ - port: opts.port, - core: opts.core, - timeDelay: opts.timeDelay, - blockheights: make(map[msg.ChainId]blockHeightInfo), - } -} - -// Start starts the http metrics server -func (s httpMetricServer) Start() { - log.Info("Health status server started listening on", "port", s.port) - - // Setup routes - http.HandleFunc("/health", s.healthStatus) - - // Start http server - err := http.ListenAndServe(":"+strconv.Itoa(s.port), nil) - - if err == http.ErrServerClosed { - log.Info("Health status server is shutting down", err) - } else { - log.Error("Health status server shutting down, server error: ", err) - } -} - -// healthStatus is a catch-all update that grabs the latest updates on the running chains -// It assumes that the configuration was set correctly, therefore the relevant chains are -// only those that are in the core.Core registry. -func (s httpMetricServer) healthStatus(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - - // Grab all chains - chains := s.core.Registry - - // Iterate through their block heads and update the cache accordingly - for _, chain := range chains { - latestBlock := chain.GetLatestBlock() - - // Get old blockheight - if prevHeight, ok := s.blockheights[chain.Id()]; ok { - // Note The listener performing the polling should already be accounting for the block delay - // TODO Account for timestamps - timeDiff := latestBlock.Timestamp.Sub(prevHeight.lastUpdated) - if latestBlock.Height.Cmp(prevHeight.height) >= 0 && int(timeDiff.Seconds()) < s.timeDelay { - s.blockheights[chain.Id()] = blockHeightInfo{ - height: latestBlock.Height, - lastUpdated: latestBlock.Timestamp, - } - } else { - if int(timeDiff.Seconds()) > s.timeDelay { - // Error if we exceeded the time limit - response := &httpResponse{ - Data: "", - Error: fmt.Sprintf("%s%s%s%s", "Chain height hasn't changed in: ", timeDiff, " Current height: ", latestBlock.Height), - } - w.WriteHeader(http.StatusInternalServerError) - err := json.NewEncoder(w).Encode(response) - if err != nil { - log.Error("Failed to write, chain height hasn't changed in: %d seconds, Current height: %d", timeDiff.Seconds(), latestBlock.Height, err) - } - return - } else { - // Error for having a smaller blockheight than previous - response := &httpResponse{ - Data: "", - Error: fmt.Sprintf("%s%s%s%s%s", "latestBlock is <= previousHeight ", "previousHeight: ", prevHeight.height, "latestBlock: ", latestBlock.Height), - } - w.WriteHeader(http.StatusInternalServerError) - err := json.NewEncoder(w).Encode(response) - if err != nil { - log.Error("Failed to write, latest height less than previous height, latest height: %d, previous height: %d", latestBlock.Height, prevHeight.height, err) - } - return - } - } - } else { - // Note: Could be edge case where chain never started, perhaps push initialization to different step - // First time we've received a block for this chain - s.blockheights[chain.Id()] = blockHeightInfo{ - height: latestBlock.Height, - lastUpdated: latestBlock.Timestamp, - } - } - } - response := &httpResponse{ - Data: "200 - operational", - Error: "", - } - w.WriteHeader(http.StatusOK) - err := json.NewEncoder(w).Encode(response) - if err != nil { - log.Error("Failed to write: 200 - Operational") - } -} From e674a63404ea1d4b3f7d99b838dfa2879499360e Mon Sep 17 00:00:00 2001 From: David Ansermino Date: Wed, 26 Aug 2020 10:09:26 -0400 Subject: [PATCH 19/21] Adds cli flags --- cmd/chainbridge/main.go | 12 ++++++++---- config/flags.go | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/cmd/chainbridge/main.go b/cmd/chainbridge/main.go index acda31262..af8db3d19 100644 --- a/cmd/chainbridge/main.go +++ b/cmd/chainbridge/main.go @@ -17,7 +17,7 @@ import ( "github.com/ChainSafe/ChainBridge/config" "github.com/ChainSafe/ChainBridge/core" msg "github.com/ChainSafe/ChainBridge/message" - "github.com/ChainSafe/ChainBridge/monitor/health" + "github.com/ChainSafe/ChainBridge/metrics/health" log "github.com/ChainSafe/log15" "github.com/urfave/cli/v2" ) @@ -31,6 +31,8 @@ var cliFlags = []cli.Flag{ config.BlockstorePathFlag, config.FreshStartFlag, config.LatestBlockFlag, + config.MetricsFlag, + config.MetricsPort, } var generateFlags = []cli.Flag{ @@ -189,9 +191,11 @@ func run(ctx *cli.Context) error { c.AddChain(newChain) } - go func() { - health.Start(c) - }() + // Start metrics server + if ctx.Bool(config.MetricsFlag.Name) { + port := ctx.Int(config.MetricsPort.Name) + go health.Start(port, c.Registry) + } c.Start() diff --git a/config/flags.go b/config/flags.go index 945384b3e..61eaa88c1 100644 --- a/config/flags.go +++ b/config/flags.go @@ -43,6 +43,20 @@ var ( } ) +// Metrics flags +var ( + MetricsFlag = &cli.BoolFlag{ + Name: "metrics", + Usage: "Enables metric server", + } + + MetricsPort = &cli.IntFlag{ + Name: "metricsPort", + Usage: "Port to serve metrics on", + Value: 8001, + } +) + // Generate subcommand flags var ( PasswordFlag = &cli.StringFlag{ From 267f0ece0964a86fb1c9c05cb5d49c345c1e1859 Mon Sep 17 00:00:00 2001 From: David Ansermino Date: Wed, 26 Aug 2020 10:17:33 -0400 Subject: [PATCH 20/21] Cleanup --- chains/ethereum/listener.go | 2 +- test-config.json | 21 --------------------- 2 files changed, 1 insertion(+), 22 deletions(-) delete mode 100644 test-config.json diff --git a/chains/ethereum/listener.go b/chains/ethereum/listener.go index 2506686af..171149e53 100644 --- a/chains/ethereum/listener.go +++ b/chains/ethereum/listener.go @@ -135,7 +135,7 @@ func (l *listener) pollBlocks() error { // Goto next block and reset retry counter currentBlock.Add(currentBlock, big.NewInt(1)) - l.latestBlock.Height = latestBlock + l.latestBlock.Height = big.NewInt(0).Set(latestBlock) l.latestBlock.LastUpdated = time.Now() retry = BlockRetryLimit } diff --git a/test-config.json b/test-config.json deleted file mode 100644 index e5e8cb372..000000000 --- a/test-config.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "Chains": [ - { - "name": "goerli", - "type": "ethereum", - "id": "0", - "endpoint": "ws://localhost:8545", - "from": "0x02642514dbebb135043cdbae4599a04a2ea84c9bcd701575d77d1a4c7cb2b977e0", - "opts": { - "bridge": "0x62877dDCd49aD22f5eDfc6ac108e9a4b5D2bD88B", - "erc20Handler": "0x3167776db165D8eA0f51790CA2bbf44Db5105ADF", - "erc721Handler": "0x3f709398808af36ADBA86ACC617FeB7F5B7B193E", - "genericHandler": "0x2B6Ab4b880A45a07d83Cf4d664Df4Ab85705Bc07", - "gasLimit": "1000000", - "maxGasPrice": "20000000", - "startBlock": "0", - "http": "false" - } - } - ] -} \ No newline at end of file From 8a03d65780ada6846ef9609f4d8b553bcd4e7825 Mon Sep 17 00:00:00 2001 From: David Ansermino Date: Wed, 26 Aug 2020 10:26:29 -0400 Subject: [PATCH 21/21] Adds missing license headers --- README.md | 1 - metrics/types/types.go | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 70776c630..bf82a35e7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ [![Build Status](https://travis-ci.com/ChainSafe/ChainBridge.svg?branch=master)](https://travis-ci.com/ChainSafe/ChainBridge) -

[WIP]

# Contents diff --git a/metrics/types/types.go b/metrics/types/types.go index b22cf7e46..cf0fb4dfa 100644 --- a/metrics/types/types.go +++ b/metrics/types/types.go @@ -1,3 +1,6 @@ +// Copyright 2020 ChainSafe Systems +// SPDX-License-Identifier: LGPL-3.0-only + package types import (