Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • hackjealousy/thornode
  • thorchain/thornode
  • lpfloyd/thornode
  • balder7/thornode
  • Gaiieg/thornode
  • vivek.vardhan7/thornode
  • yairi.medinac/thornode
  • alessio/thornode
  • stjordanis/thornode
  • jtakalai1/thornode
  • ggulgun/thornode
  • pascaldekloe/thornode
  • n.huskisson1992/thornode
  • horacio.mlequo/thornode
  • blockx-labs/thornode
  • silverbackgodx/thornode
  • aleksbez/thornode
  • kushptl/thornode
  • difordcrypt/thornode
  • 5thdimension/thornode
  • edgarmanuelruizplasticos/thornode
  • DevLopME-Az/thornode
  • aswizzle/thornode
  • kiasaki/thornode
  • zhangliang041/thornode
  • cartersz/thornode
  • vikingshield/thornode
  • crzyazblue1/thornode
  • vkbdev/thornode
  • nathanaafo/thornode
  • bi23com_guard/thornode
  • crux25/thornode
  • TheArchitect108/thornode
  • zby121103/thornode
  • the_eridanus/thornode
  • halley9r/thornode
  • pendergrassjohn288/thornode
  • faisal1389/thornode
  • alexdcox/thornode
  • huginntc/thornode
  • guidovranken/thornode
  • thorCatStevens/thornode
  • mogarchy/thornode
  • blackprotocol/blacknode
  • pranav292gpt/thornode
  • dp49/thornode
  • Bevan96230395/thornode
  • JonathanLorimer/thornode
  • akil27/thornode
  • assafmo/thornode
  • Multipartite/thornode
  • george_s/thornode
  • thehuman/thornode
  • 0x-General/thornode
  • kaladinlight/thornode
  • oliver154/thornode
  • TreefeedXavier/thornode
  • veado/thornode
  • HooriRn/thornode
  • PaperNautilus/thornode
  • scorchedfire/thornode
  • KevinXdefi/thornode
  • canziclark24/thornode
  • GiMa-Maya/blacknode
  • ajabhishek759/thornode
  • akincibor/thornode
  • digitaldollarchain/digital-dollar-node
  • rufus.t.firefly/thornode
  • lends/thornode
  • mohs1n/thornode
  • SaifulJnU/thornode
  • SamusElderg/thornode
  • Hippocampus.web3/thornode
  • TxCorpi0x/thornode
  • ursa9r/thornode
  • mdanny1209/thornode
  • OxQuasar/thornode
  • justinvforvendetta/thornode
  • pluto_x1/thornode
  • cryptobuks/thornode
  • samyap/thornode
  • AsmundTHORSec/thornode
  • jiecut42/thornode
  • fishtail6993/thornode
  • koitsu/thornode
  • TheRagnarLodbrok/thornode
  • leonalistingservice/thornode
  • cosminl/thornode
  • zlyzol/thornode
  • inkthorchain/thornode
  • dyns/thornode
  • OKEAMAH/thornode
  • kirtixs/thornode
  • asamere/thornode
  • codehans/thornode
  • markfromdenmark/thornode
  • starsquid/thornode
  • danbryan1/thornode
  • jonreiter/thornode
  • beorn_9r/thornode
  • ahdzib/thornode
  • aper.cu/thornode
  • rekt0x/thornode
  • pharr117/thornode
  • gima-swapkit/thornode
  • proof.of.steve/thornode
  • proof.of.steve/thor-node-2
  • mayachain/thorchain/thornode
  • kocubinski/thornode
  • mattshields/thorchain/thornode
110 results
Show changes
Commits on Source (53)
Showing
with 272 additions and 197 deletions
......@@ -6,3 +6,4 @@
plugins
user_trunk.yaml
user.yaml
tmp
version: 0.1
cli:
version: 1.17.1
version: 1.20.1
repo:
trunk_branch: develop
lint:
......@@ -20,19 +20,19 @@ lint:
#- trivy@0.43.1
#- trufflehog@3.44.0
- codespell@2.2.6
- black@23.9.1
- flake8@6.1.0
- black@24.2.0
- flake8@7.0.0
- git-diff-check
- gofmt@1.20.4
- golangci-lint@1.55.1
- golangci-lint@1.56.2
- hadolint@2.12.0
- isort@5.12.0
- markdownlint@0.37.0
- prettier@3.0.3
- isort@5.13.2
- markdownlint@0.39.0
- prettier@3.2.5
- shellcheck@0.9.0
- shfmt@3.6.0
- taplo@0.8.1
- yamllint@1.32.0
- yamllint@1.35.1
ignore:
- linters: [ALL]
paths:
......@@ -71,7 +71,7 @@ actions:
plugins:
sources:
- id: trunk
ref: v1.2.6
ref: v1.4.3
uri: https://github.com/trunk-io/plugins
runtimes:
enabled:
......
......@@ -56,7 +56,6 @@ BUILDTAG?=$(shell git rev-parse --abbrev-ref HEAD)
# ------------------------------ Generate ------------------------------
generate: go-generate openapi protob-docker
@git ls-files openapi/gen | xargs sed -i '/^[- ]*API version.*$(shell cat version)/d;/APIClient.*$(shell cat version)/d'
go-generate:
@go install golang.org/x/tools/cmd/stringer@v0.15.0
......@@ -97,6 +96,8 @@ openapi:
openapitools/openapi-generator-cli:v6.0.0@sha256:310bd0353c11863c0e51e5cb46035c9e0778d4b9c6fe6a7fc8307b3b41997a35 \
generate -i /mnt/openapi.yaml -g go -o /mnt/gen
@rm openapi/gen/go.mod openapi/gen/go.sum
@find ./openapi/gen -type f | xargs sed -i '/^[- ]*API version.*$(shell cat version)/d;/APIClient.*$(shell cat version)/d'
@find ./openapi/gen -type f | grep model | xargs sed -i 's/MarshalJSON(/MarshalJSON_deprecated(/'
# ------------------------------ Docs ------------------------------
......@@ -105,6 +106,7 @@ docs-init:
@cargo install mdbook-admonish --version 1.14.0
@cargo install mdbook-catppuccin --version 2.1.0
@cargo install mdbook-katex --version 0.5.9
@cargo install mdbook-embed --version 0.2.0
@cd docs && mdbook-catppuccin install
@cd docs && mdbook-admonish install --css-dir theme
......@@ -139,18 +141,12 @@ format:
lint:
@./scripts/lint.sh
@go run tools/analyze/main.go ./common/... ./constants/... ./x/... ./mimir/...
@go run tools/lint-whitelist-tokens/main.go
@./scripts/trunk check --no-fix --upstream origin/develop
lint-ci:
@./scripts/lint.sh
@go run tools/analyze/main.go ./common/... ./constants/... ./x/... ./mimir/...
@go run tools/lint-whitelist-tokens/main.go
@./scripts/lint-versions.bash
@./scripts/lint-mimir-ids.bash
ifdef CI_MERGE_REQUEST_ID
# only check changes on merge requests
# only check changes on merge requests
@./scripts/trunk check --ci -j8 --upstream FETCH_HEAD
else
@./scripts/trunk check --all --ci -j8
......
......@@ -83,7 +83,6 @@ The slip-based fee model has the following benefits:
- Resistant to manipulation
- A proxy for demand of liquidity
- Asymptotes to zero over time, ensuring pool prices match reference prices
- Prevents Impermanent Loss to liquidity providers
**Provide Liquidity**
The liquidity units awarded to a liquidity provider is given by:
......
......@@ -420,9 +420,7 @@ func (b *Binance) sign(bnbSignMsg btx.StdSignMsg, poolPubKey common.PubKey) ([]b
if b.localKeyManager.Pubkey().Equals(poolPubKey) {
return b.localKeyManager.Sign(bnbSignMsg)
}
res, _, err := b.tssKeyManager.RemoteSign(bnbSignMsg.Data, poolPubKey.String())
return res, err
return b.tssKeyManager.SignWithPool(bnbSignMsg, poolPubKey)
}
// signMsg is design to sign a given message until it success or the same message had been send out by other signer
......@@ -617,33 +615,69 @@ func (b *Binance) ReportSolvency(bnbBlockHeight int64) error {
if !b.ShouldReportSolvency(bnbBlockHeight) {
return nil
}
// blockchain scanner is catching up , no solvency check messages
// when block scanner is not healthy, only report from auto-unhalt SolvencyCheckRunner
// (FetchTxs passes currentScanningHeight, while SolvencyCheckRunner passes chainHeight)
if !b.IsBlockScannerHealthy() && bnbBlockHeight == b.bnbScanner.currentScanningHeight {
return nil
}
// fetch all asgard vaults
asgardVaults, err := b.thorchainBridge.GetAsgards()
if err != nil {
return fmt.Errorf("fail to get asgards,err: %w", err)
}
for _, asgard := range asgardVaults {
currentGasFee := cosmos.NewUint(3 * b.bnbScanner.singleFee)
// report insolvent asgard vaults,
// or else all if the chain is halted and all are solvent
msgs := make([]stypes.Solvency, 0, len(asgardVaults))
solventMsgs := make([]stypes.Solvency, 0, len(asgardVaults))
for i := range asgardVaults {
var acct common.Account
acct, err = b.GetAccount(asgard.PubKey, nil)
acct, err = b.GetAccount(asgardVaults[i].PubKey, nil)
if err != nil {
b.logger.Err(err).Msgf("fail to get account balance")
continue
}
if runners.IsVaultSolvent(acct, asgard, cosmos.NewUint(3*b.bnbScanner.singleFee)) && b.IsBlockScannerHealthy() {
// when vault is solvent , don't need to report solvency
continue
}
select {
case b.globalSolvencyQueue <- stypes.Solvency{
msg := stypes.Solvency{
Height: bnbBlockHeight,
Chain: common.BNBChain,
PubKey: asgard.PubKey,
PubKey: asgardVaults[i].PubKey,
Coins: acct.Coins,
}:
}
if runners.IsVaultSolvent(acct, asgardVaults[i], currentGasFee) {
solventMsgs = append(solventMsgs, msg) // Solvent-vault message
continue
}
msgs = append(msgs, msg) // Insolvent-vault message
}
// Only if the block scanner is unhealthy (e.g. solvency-halted) and all vaults are solvent,
// report that all the vaults are solvent.
// If there are any insolvent vaults, report only them.
// Not reporting both solvent and insolvent vaults is to avoid noise (spam):
// Reporting both could halt-and-unhalt SolvencyHalt in the same THOR block
// (resetting its height), plus making it harder to know at a glance from solvency reports which vaults were insolvent.
solvent := false
if !b.IsBlockScannerHealthy() && len(solventMsgs) == len(asgardVaults) {
msgs = solventMsgs
solvent = true
}
for i := range msgs {
b.logger.Info().
Stringer("asgard", msgs[i].PubKey).
Interface("coins", msgs[i].Coins).
Bool("solvent", solvent).
Msg("reporting solvency")
// send solvency to thorchain via global queue consumed by the observer
select {
case b.globalSolvencyQueue <- msgs[i]:
case <-time.After(constants.ThorchainBlockTime):
b.logger.Info().Msgf("fail to send solvency info to THORChain, timeout")
}
......
......@@ -961,34 +961,69 @@ func (c *Client) ReportSolvency(ethBlockHeight int64) error {
if !c.ShouldReportSolvency(ethBlockHeight) {
return nil
}
// when block scanner is not healthy , falling behind , we don't report solvency , unless the request is coming from
// auto-unhalt solvency runner
// when block scanner is not healthy, only report from auto-unhalt SolvencyCheckRunner
// (FetchTxs passes currentBlockHeight, while SolvencyCheckRunner passes chainHeight)
if !c.IsBlockScannerHealthy() && ethBlockHeight == c.ethScanner.currentBlockHeight {
return nil
}
// fetch all asgard vaults
asgardVaults, err := c.bridge.GetAsgards()
if err != nil {
return fmt.Errorf("fail to get asgards,err: %w", err)
}
for _, asgard := range asgardVaults {
currentGasFee := cosmos.NewUint(3 * c.cfg.BlockScanner.MaxGasLimit * c.ethScanner.lastReportedGasPrice)
// report insolvent asgard vaults,
// or else all if the chain is halted and all are solvent
msgs := make([]stypes.Solvency, 0, len(asgardVaults))
solventMsgs := make([]stypes.Solvency, 0, len(asgardVaults))
for i := range asgardVaults {
var acct common.Account
acct, err = c.GetAccount(asgard.PubKey, new(big.Int).SetInt64(ethBlockHeight))
acct, err = c.GetAccount(asgardVaults[i].PubKey, new(big.Int).SetInt64(ethBlockHeight))
if err != nil {
c.logger.Err(err).Msgf("fail to get account balance")
continue
}
if runners.IsVaultSolvent(acct, asgard, cosmos.NewUint(3*c.cfg.BlockScanner.MaxGasLimit*c.ethScanner.lastReportedGasPrice)) && c.IsBlockScannerHealthy() {
// when vault is solvent , don't need to report solvency
// when block scanner is not healthy , usually that means the chain is halted , in that scenario , we continue to report solvency
continue
}
select {
case c.globalSolvencyQueue <- stypes.Solvency{
msg := stypes.Solvency{
Height: ethBlockHeight,
Chain: common.ETHChain,
PubKey: asgard.PubKey,
PubKey: asgardVaults[i].PubKey,
Coins: acct.Coins,
}:
}
if runners.IsVaultSolvent(acct, asgardVaults[i], currentGasFee) {
solventMsgs = append(solventMsgs, msg) // Solvent-vault message
continue
}
msgs = append(msgs, msg) // Insolvent-vault message
}
// Only if the block scanner is unhealthy (e.g. solvency-halted) and all vaults are solvent,
// report that all the vaults are solvent.
// If there are any insolvent vaults, report only them.
// Not reporting both solvent and insolvent vaults is to avoid noise (spam):
// Reporting both could halt-and-unhalt SolvencyHalt in the same THOR block
// (resetting its height), plus making it harder to know at a glance from solvency reports which vaults were insolvent.
solvent := false
if !c.IsBlockScannerHealthy() && len(solventMsgs) == len(asgardVaults) {
msgs = solventMsgs
solvent = true
}
for i := range msgs {
c.logger.Info().
Stringer("asgard", msgs[i].PubKey).
Interface("coins", msgs[i].Coins).
Bool("solvent", solvent).
Msg("reporting solvency")
// send solvency to thorchain via global queue consumed by the observer
select {
case c.globalSolvencyQueue <- msgs[i]:
case <-time.After(constants.ThorchainBlockTime):
c.logger.Info().Msgf("fail to send solvency info to THORChain, timeout")
}
......
......@@ -381,6 +381,11 @@ func (e *ETHScanner) extractTxs(block *etypes.Block) (stypes.TxIn, error) {
// process txs in parallel
for _, tx := range block.Transactions() {
// skip blob transactions
if tx.Type() == etypes.BlobTxType {
continue
}
wg.Add(1)
go processTx(tx)
}
......
......@@ -300,13 +300,13 @@ func (s *BlockScannerTestSuite) TestFromTxToTxIn(c *C) {
c.Assert(err, IsNil)
}
if rpcRequest.Method == "eth_call" {
if string(rpcRequest.Params) == `[{"data":"0x95d89b41","from":"0x0000000000000000000000000000000000000000","to":"0x3b7fa4dd21c6f9ba3ca375217ead7cab9d6bf483"},"latest"]` ||
if string(rpcRequest.Params) == `[{"from":"0x0000000000000000000000000000000000000000","input":"0x95d89b41","to":"0x3b7fa4dd21c6f9ba3ca375217ead7cab9d6bf483"},"latest"]` ||
string(rpcRequest.Params) == `[{"data":"0x95d89b41","from":"0x0000000000000000000000000000000000000000","to":"0x40bcd4db8889a8bf0b1391d0c819dcd9627f9d0a"},"latest"]` {
_, err = rw.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003544b4e0000000000000000000000000000000000000000000000000000000000"}`))
_, err = rw.Write([]byte(`{"jsonrpc":"2.0","id":3,"result":"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003544b4e0000000000000000000000000000000000000000000000000000000000"}`))
c.Assert(err, IsNil)
return
} else if string(rpcRequest.Params) == `[{"data":"0x313ce567","from":"0x0000000000000000000000000000000000000000","to":"0x3b7fa4dd21c6f9ba3ca375217ead7cab9d6bf483"},"latest"]` {
_, err = rw.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0x0000000000000000000000000000000000000000000000000000000000000012"}`))
} else if string(rpcRequest.Params) == `[{"from":"0x0000000000000000000000000000000000000000","input":"0x313ce567","to":"0x3b7fa4dd21c6f9ba3ca375217ead7cab9d6bf483"},"latest"]` {
_, err = rw.Write([]byte(`{"jsonrpc":"2.0","id":4,"result":"0x0000000000000000000000000000000000000000000000000000000000000012"}`))
c.Assert(err, IsNil)
return
}
......
......@@ -182,8 +182,8 @@ func (s *EthereumSuite) SetUpTest(c *C) {
c.Assert(err, IsNil)
}
if rpcRequest.Method == "eth_call" {
if string(rpcRequest.Params) == `[{"data":"0x03b6a6730000000000000000000000009f4aab49a9cd8fc54dcb3701846f608a6f2c44da0000000000000000000000003b7fa4dd21c6f9ba3ca375217ead7cab9d6bf483","from":"0x9f4aab49a9cd8fc54dcb3701846f608a6f2c44da","to":"0xe65e9d372f8cacc7b6dfcd4af6507851ed31bb44"},"latest"]` {
_, err = rw.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0x0000000000000000000000000000000000000000000000000000000000000012"}`))
if string(rpcRequest.Params) == `[{"from":"0x9f4aab49a9cd8fc54dcb3701846f608a6f2c44da","input":"0x03b6a6730000000000000000000000009f4aab49a9cd8fc54dcb3701846f608a6f2c44da0000000000000000000000003b7fa4dd21c6f9ba3ca375217ead7cab9d6bf483","to":"0xe65e9d372f8cacc7b6dfcd4af6507851ed31bb44"},"latest"]` {
_, err = rw.Write([]byte(`{"jsonrpc":"2.0","id":5,"result":"0x0000000000000000000000000000000000000000000000000000000000000012"}`))
c.Assert(err, IsNil)
} else if string(rpcRequest.Params) == `[{"data":"0x95d89b41","from":"0x0000000000000000000000000000000000000000","to":"0x3b7fa4dd21c6f9ba3ca375217ead7cab9d6bf483"},"latest"]` {
_, err = rw.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003544b4e0000000000000000000000000000000000000000000000000000000000"}`))
......
......@@ -845,11 +845,13 @@ func (c *EVMClient) ReportSolvency(height int64) error {
return nil
}
// skip reporting solvency if the block scanner is unhealthy and we are synced
// when block scanner is not healthy, only report from auto-unhalt SolvencyCheckRunner
// (FetchTxs passes currentBlockHeight, while SolvencyCheckRunner passes chainHeight)
if !c.IsBlockScannerHealthy() && height == c.evmScanner.currentBlockHeight {
return nil
}
// fetch all asgard vaults
asgardVaults, err := c.bridge.GetAsgards()
if err != nil {
return fmt.Errorf("fail to get asgards, err: %w", err)
......@@ -857,32 +859,54 @@ func (c *EVMClient) ReportSolvency(height int64) error {
currentGasFee := cosmos.NewUint(3 * c.cfg.BlockScanner.MaxGasLimit * c.evmScanner.lastReportedGasPrice)
for _, asgard := range asgardVaults {
// report insolvent asgard vaults,
// or else all if the chain is halted and all are solvent
msgs := make([]stypes.Solvency, 0, len(asgardVaults))
solventMsgs := make([]stypes.Solvency, 0, len(asgardVaults))
for i := range asgardVaults {
var acct common.Account
acct, err = c.GetAccount(asgard.PubKey, new(big.Int).SetInt64(height))
acct, err = c.GetAccount(asgardVaults[i].PubKey, new(big.Int).SetInt64(height))
if err != nil {
c.logger.Err(err).Msg("fail to get account balance")
continue
}
// skip reporting solvency if the account is solvent and block scanner is healthy
solvent := runners.IsVaultSolvent(acct, asgard, currentGasFee)
if solvent && c.IsBlockScannerHealthy() {
msg := stypes.Solvency{
Height: height,
Chain: c.cfg.ChainID,
PubKey: asgardVaults[i].PubKey,
Coins: acct.Coins,
}
if runners.IsVaultSolvent(acct, asgardVaults[i], currentGasFee) {
solventMsgs = append(solventMsgs, msg) // Solvent-vault message
continue
}
msgs = append(msgs, msg) // Insolvent-vault message
}
// Only if the block scanner is unhealthy (e.g. solvency-halted) and all vaults are solvent,
// report that all the vaults are solvent.
// If there are any insolvent vaults, report only them.
// Not reporting both solvent and insolvent vaults is to avoid noise (spam):
// Reporting both could halt-and-unhalt SolvencyHalt in the same THOR block
// (resetting its height), plus making it harder to know at a glance from solvency reports which vaults were insolvent.
solvent := false
if !c.IsBlockScannerHealthy() && len(solventMsgs) == len(asgardVaults) {
msgs = solventMsgs
solvent = true
}
for i := range msgs {
c.logger.Info().
Stringer("asgard", asgard.PubKey).
Interface("coins", acct.Coins).
Stringer("asgard", msgs[i].PubKey).
Interface("coins", msgs[i].Coins).
Bool("solvent", solvent).
Msg("reporting solvency")
// send solvency to thorchain via global queue consumed by the observer
select {
case c.globalSolvencyQueue <- stypes.Solvency{
Height: height,
Chain: c.cfg.ChainID,
PubKey: asgard.PubKey,
Coins: acct.Coins,
}:
case c.globalSolvencyQueue <- msgs[i]:
case <-time.After(constants.ThorchainBlockTime):
c.logger.Info().Msg("fail to send solvency info to thorchain, timeout")
}
......
......@@ -118,7 +118,7 @@ func (s *UnstuckTestSuite) SetUpTest(c *C) {
c.Assert(params[0], Equals, lastBroadcastTx)
_, err = rw.Write([]byte(`[{
"jsonrpc": "2.0",
"id": 1,
"id": 5,
"result": {
"blockHash": "0x96395fbdb39e33293999dc1a0a3b87c8a9e51185e177760d1482c2155bb35b87",
"blockNumber": "0x1",
......
......@@ -562,27 +562,69 @@ func (c *CosmosClient) ReportSolvency(blockHeight int64) error {
if !c.ShouldReportSolvency(blockHeight) {
return nil
}
// when block scanner is not healthy, only report from auto-unhalt SolvencyCheckRunner
// (FetchTxs passes PreviousHeight + 1 from scanBlocks, while SolvencyCheckRunner passes chainHeight)
if !c.IsBlockScannerHealthy() && blockHeight == c.blockScanner.PreviousHeight()+1 {
return nil
}
// fetch all asgard vaults
asgardVaults, err := c.thorchainBridge.GetAsgards()
if err != nil {
return fmt.Errorf("fail to get asgards,err: %w", err)
}
for _, asgard := range asgardVaults {
currentGasFee := c.cosmosScanner.lastFee
// report insolvent asgard vaults,
// or else all if the chain is halted and all are solvent
msgs := make([]stypes.Solvency, 0, len(asgardVaults))
solventMsgs := make([]stypes.Solvency, 0, len(asgardVaults))
for i := range asgardVaults {
var acct common.Account
acct, err = c.GetAccount(asgard.PubKey, new(big.Int).SetInt64(blockHeight))
acct, err = c.GetAccount(asgardVaults[i].PubKey, new(big.Int).SetInt64(blockHeight))
if err != nil {
c.logger.Err(err).Msgf("fail to get account balance")
continue
}
if runners.IsVaultSolvent(acct, asgard, c.cosmosScanner.lastFee) && c.IsBlockScannerHealthy() {
continue
}
select {
case c.globalSolvencyQueue <- stypes.Solvency{
msg := stypes.Solvency{
Height: blockHeight,
Chain: c.cfg.ChainID,
PubKey: asgard.PubKey,
PubKey: asgardVaults[i].PubKey,
Coins: acct.Coins,
}:
}
if runners.IsVaultSolvent(acct, asgardVaults[i], currentGasFee) {
solventMsgs = append(solventMsgs, msg) // Solvent-vault message
continue
}
msgs = append(msgs, msg) // Insolvent-vault message
}
// Only if the block scanner is unhealthy (e.g. solvency-halted) and all vaults are solvent,
// report that all the vaults are solvent.
// If there are any insolvent vaults, report only them.
// Not reporting both solvent and insolvent vaults is to avoid noise (spam):
// Reporting both could halt-and-unhalt SolvencyHalt in the same THOR block
// (resetting its height), plus making it harder to know at a glance from solvency reports which vaults were insolvent.
solvent := false
if !c.IsBlockScannerHealthy() && len(solventMsgs) == len(asgardVaults) {
msgs = solventMsgs
solvent = true
}
for i := range msgs {
c.logger.Info().
Stringer("asgard", msgs[i].PubKey).
Interface("coins", msgs[i].Coins).
Bool("solvent", solvent).
Msg("reporting solvency")
// send solvency to thorchain via global queue consumed by the observer
select {
case c.globalSolvencyQueue <- msgs[i]:
case <-time.After(constants.ThorchainBlockTime):
c.logger.Info().Msgf("fail to send solvency info to THORChain, timeout")
}
......
......@@ -29,7 +29,7 @@ func GetAsgardAddress(chain common.Chain, bridge thorclient.ThorchainBridge) ([]
}
func GetConfMulBasisPoint(chain string, bridge thorclient.ThorchainBridge) (cosmos.Uint, error) {
confMultiplier, err := bridge.GetMimir(fmt.Sprintf("%d-%s", mimir.ConfMultiplierBasisPointsId, chain))
confMultiplier, err := bridge.GetMimir(fmt.Sprintf("%d-%s", mimir.ConfMultiplierBasisPoints, chain))
// should never be negative
if err != nil || confMultiplier <= 0 {
return cosmos.NewUint(constants.MaxBasisPts), err
......@@ -38,7 +38,7 @@ func GetConfMulBasisPoint(chain string, bridge thorclient.ThorchainBridge) (cosm
}
func MaxConfAdjustment(confirm uint64, chain string, bridge thorclient.ThorchainBridge) (uint64, error) {
maxConfirmations, err := bridge.GetMimir(fmt.Sprintf("%d-%s", mimir.MaxConfirmationsId, chain))
maxConfirmations, err := bridge.GetMimir(fmt.Sprintf("%d-%s", mimir.MaxConfirmations, chain))
if err != nil {
return confirm, err
}
......
......@@ -29,7 +29,7 @@ func (c *Client) getChainCfgBTC() *btcchaincfg.Params {
}
}
func (c *Client) signUTXOBTC(redeemTx *btcwire.MsgTx, tx stypes.TxOutItem, amount int64, sourceScript []byte, idx int, thorchainHeight int64) error {
func (c *Client) signUTXOBTC(redeemTx *btcwire.MsgTx, tx stypes.TxOutItem, amount int64, sourceScript []byte, idx int) error {
sigHashes := btctxscript.NewTxSigHashes(redeemTx)
var signable btctxscript.Signable
......
......@@ -98,7 +98,6 @@ func (s *BitcoinSuite) SetUpTest(c *C) {
s.cfg.UTXO.MaxMempoolBatches = 10
s.cfg.UTXO.EstimatedAverageTxSize = 1000
s.cfg.UTXO.MaxReorgRescanBlocks = 1
s.cfg.UTXO.GetBlockVerboseTxsAvailable = true
ns := strconv.Itoa(time.Now().Nanosecond())
thordir := filepath.Join(os.TempDir(), ns, ".thorcli")
......
......@@ -26,7 +26,7 @@ func (c *Client) getChainCfgBCH() *bchchaincfg.Params {
}
}
func (c *Client) signUTXOBCH(redeemTx *bchwire.MsgTx, tx stypes.TxOutItem, amount int64, sourceScript []byte, idx int, thorchainHeight int64) error {
func (c *Client) signUTXOBCH(redeemTx *bchwire.MsgTx, tx stypes.TxOutItem, amount int64, sourceScript []byte, idx int) error {
var signable bchtxscript.Signable
if tx.VaultPubKey.Equals(c.nodePubKey) {
signable = bchtxscript.NewPrivateKeySignable((*bchec.PrivateKey)(c.nodePrivKey))
......
......@@ -97,7 +97,6 @@ func (s *BitcoinCashSuite) SetUpTest(c *C) {
s.cfg.UTXO.MaxMempoolBatches = 10
s.cfg.UTXO.EstimatedAverageTxSize = 1500
s.cfg.UTXO.MaxReorgRescanBlocks = 1
s.cfg.UTXO.GetBlockVerboseTxsAvailable = true
ns := strconv.Itoa(time.Now().Nanosecond())
thordir := filepath.Join(os.TempDir(), ns, ".thorcli")
......
......@@ -115,8 +115,10 @@ func NewClient(
return nil, fmt.Errorf("unsupported utxo chain: %s", cfg.ChainID)
}
logger := log.Logger.With().Stringer("chain", cfg.ChainID).Logger()
// create rpc client
rpcClient, err := rpc.NewClient(cfg.RPCHost, cfg.UserName, cfg.Password, cfg.UTXO.Version, cfg.MaxRPCRetries)
rpcClient, err := rpc.NewClient(cfg.RPCHost, cfg.UserName, cfg.Password, cfg.MaxRPCRetries, logger)
if err != nil {
return nil, fmt.Errorf("fail to create rpc client: %w", err)
}
......@@ -139,7 +141,7 @@ func NewClient(
// create base client
c := &Client{
cfg: cfg,
log: log.Logger.With().Stringer("chain", cfg.ChainID).Logger(),
log: logger,
m: m,
rpc: rpcClient,
nodePubKey: nodePubKey,
......@@ -475,6 +477,7 @@ func (c *Client) FetchTxs(height, chainHeight int64) (types.TxIn, error) {
if err != nil {
c.log.Err(err).Msg("fail to send network fee")
}
// when block scanner is not healthy, only report from auto-unhalt SolvencyCheckRunner
if c.IsBlockScannerHealthy() {
if err = c.ReportSolvency(height); err != nil {
c.log.Err(err).Msg("fail to report solvency info")
......@@ -696,29 +699,56 @@ func (c *Client) ReportSolvency(height int64) error {
return fmt.Errorf("fail to get asgards: %w", err)
}
// report solvency for each asgard vault
for _, asgard := range asgardVaults {
currentGasFee := cosmos.NewUint(3 * c.cfg.UTXO.EstimatedAverageTxSize * c.lastFeeRate)
// report insolvent asgard vaults,
// or else all if the chain is halted and all are solvent
msgs := make([]types.Solvency, 0, len(asgardVaults))
solventMsgs := make([]types.Solvency, 0, len(asgardVaults))
for i := range asgardVaults {
var acct common.Account
acct, err = c.GetAccount(asgard.PubKey, nil)
acct, err = c.GetAccount(asgardVaults[i].PubKey, nil)
if err != nil {
c.log.Err(err).Msgf("fail to get account balance")
continue
}
// skip reporting if the vault is solvent (within 10x gas fee)
gasFee := cosmos.NewUint(3 * c.cfg.UTXO.EstimatedAverageTxSize * c.lastFeeRate)
if c.IsBlockScannerHealthy() && runners.IsVaultSolvent(acct, asgard, gasFee) {
msg := types.Solvency{
Height: height,
Chain: c.cfg.ChainID,
PubKey: asgardVaults[i].PubKey,
Coins: acct.Coins,
}
if runners.IsVaultSolvent(acct, asgardVaults[i], currentGasFee) {
solventMsgs = append(solventMsgs, msg) // Solvent-vault message
continue
}
msgs = append(msgs, msg) // Insolvent-vault message
}
// Only if the block scanner is unhealthy (e.g. solvency-halted) and all vaults are solvent,
// report that all the vaults are solvent.
// If there are any insolvent vaults, report only them.
// Not reporting both solvent and insolvent vaults is to avoid noise (spam):
// Reporting both could halt-and-unhalt SolvencyHalt in the same THOR block
// (resetting its height), plus making it harder to know at a glance from solvency reports which vaults were insolvent.
solvent := false
if !c.IsBlockScannerHealthy() && len(solventMsgs) == len(asgardVaults) {
msgs = solventMsgs
solvent = true
}
for i := range msgs {
c.log.Info().
Stringer("asgard", msgs[i].PubKey).
Interface("coins", msgs[i].Coins).
Bool("solvent", solvent).
Msg("reporting solvency")
// send solvency to thorchain via global queue consumed by the observer
select {
case c.globalSolvencyQueue <- types.Solvency{
Height: height,
Chain: c.cfg.ChainID,
PubKey: asgard.PubKey,
Coins: acct.Coins,
}:
case c.globalSolvencyQueue <- msgs[i]:
case <-time.After(constants.ThorchainBlockTime):
c.log.Warn().Msgf("timeout sending solvency to thorchain")
}
......
......@@ -204,11 +204,11 @@ func (c *Client) removeFromMemPoolCache(hash string) {
}
func (c *Client) tryAddToMemPoolCache(hash string) bool {
exist, err := c.temporalStorage.TrackMempoolTx(hash)
added, err := c.temporalStorage.TrackMempoolTx(hash)
if err != nil {
c.log.Err(err).Str("txid", hash).Msg("fail to add to mempool cache")
}
return exist
return added
}
func (c *Client) canDeleteBlock(blockMeta *utxo.BlockMeta) bool {
......@@ -373,11 +373,6 @@ func (c *Client) sendNetworkFeeFromBlock(blockResult *btcjson.GetBlockVerboseTxR
}
func (c *Client) getBlock(height int64) (*btcjson.GetBlockVerboseTxResult, error) {
// only relevant for DOGE before version 1.14.7
if !c.cfg.UTXO.GetBlockVerboseTxsAvailable {
return c.getBlockWithoutVerbose(height)
}
hash, err := c.rpc.GetBlockHash(height)
if err != nil {
return &btcjson.GetBlockVerboseTxResult{}, err
......@@ -385,95 +380,6 @@ func (c *Client) getBlock(height int64) (*btcjson.GetBlockVerboseTxResult, error
return c.rpc.GetBlockVerboseTxs(hash)
}
// getBlockWithoutVerbose will get the block without verbose transaction details, and
// then make batch calls to populate them. This should only be used on chains that do not
// support verbosity level 2 for getblock (currently only dogecoin).
func (c *Client) getBlockWithoutVerbose(height int64) (*btcjson.GetBlockVerboseTxResult, error) {
hash, err := c.rpc.GetBlockHash(height)
if err != nil {
return &btcjson.GetBlockVerboseTxResult{}, err
}
// get block without verbose transactions
block, err := c.rpc.GetBlockVerbose(hash)
if err != nil {
return &btcjson.GetBlockVerboseTxResult{}, err
}
// copy block data to verbose result
blockResult := btcjson.GetBlockVerboseTxResult{
Hash: block.Hash,
Confirmations: block.Confirmations,
StrippedSize: block.StrippedSize,
Size: block.Size,
Weight: block.Weight,
Height: block.Height,
Version: block.Version,
VersionHex: block.VersionHex,
MerkleRoot: block.MerkleRoot,
Time: block.Time,
Nonce: block.Nonce,
Bits: block.Bits,
Difficulty: block.Difficulty,
PreviousHash: block.PreviousHash,
NextHash: block.NextHash,
}
// create our batches
batches := [][]string{}
batch := []string{}
for _, txid := range block.Tx {
batch = append(batch, txid)
if len(batch) >= c.cfg.UTXO.TransactionBatchSize {
batches = append(batches, batch)
batch = []string{}
}
}
if len(batch) > 0 {
batches = append(batches, batch)
}
// process batch requests one at a time to avoid overloading the node
retries := 0
for i := 0; i < len(batches); i++ {
var results []*btcjson.TxRawResult
var errs []error
results, errs, err = c.rpc.BatchGetRawTransactionVerbose(batches[i])
// if there was no rpc error, check for any tx errors
txErrCount := 0
if err == nil {
for _, txErr := range errs {
if txErr != nil {
err = txErr
}
txErrCount++
}
}
// retry the batch a few times on any errors to avoid wasted work
// TODO: implement partial retry
if err != nil {
if retries >= 3 {
return &btcjson.GetBlockVerboseTxResult{}, err
}
c.log.Err(err).Int("txErrCount", txErrCount).Msgf("retrying block txs batch %d", i)
time.Sleep(time.Second)
retries++
i-- // retry the same batch
continue
}
// add transactions to block result
for _, tx := range results {
blockResult.Tx = append(blockResult.Tx, *tx)
}
}
return &blockResult, nil
}
func (c *Client) isValidUTXO(hexPubKey string) bool {
buf, decErr := hex.DecodeString(hexPubKey)
if decErr != nil {
......@@ -745,7 +651,7 @@ func (c *Client) extractTxs(block *btcjson.GetBlockVerboseTxResult) (types.TxIn,
var txInItem types.TxInItem
txInItem, err = c.getTxIn(&block.Tx[idx], block.Height, false, vinZeroTxs)
if err != nil {
c.log.Debug().Err(err).Msg("fail to get TxInItem")
c.log.Info().Err(err).Msg("fail to get TxInItem")
continue
}
if txInItem.IsEmpty() {
......@@ -757,12 +663,12 @@ func (c *Client) extractTxs(block *btcjson.GetBlockVerboseTxResult) (types.TxIn,
if txInItem.Coins[0].Amount.LT(c.cfg.ChainID.DustThreshold()) {
continue
}
var exist bool
exist, err = c.temporalStorage.TrackObservedTx(txInItem.Tx)
var added bool
added, err = c.temporalStorage.TrackObservedTx(txInItem.Tx)
if err != nil {
c.log.Err(err).Msgf("fail to determinate whether hash(%s) had been observed before", txInItem.Tx)
c.log.Err(err).Msgf("fail to determine whether hash(%s) had been observed before", txInItem.Tx)
}
if !exist {
if !added {
c.log.Info().Msgf("tx: %s had been report before, ignore", txInItem.Tx)
if err = c.temporalStorage.UntrackObservedTx(txInItem.Tx); err != nil {
c.log.Err(err).Msgf("fail to remove observed tx from cache: %s", txInItem.Tx)
......@@ -928,6 +834,11 @@ func (c *Client) getMemo(tx *btcjson.TxRawResult) (string, error) {
}
opReturnFields := strings.Fields(asm)
if len(opReturnFields) == 2 {
// skip "0" field to avoid log noise
if opReturnFields[1] == "0" {
continue
}
var decoded []byte
decoded, err = hex.DecodeString(opReturnFields[1])
if err != nil {
......
......@@ -26,7 +26,7 @@ func (c *Client) getChainCfgDOGE() *dogechaincfg.Params {
}
}
func (c *Client) signUTXODOGE(redeemTx *dogewire.MsgTx, tx stypes.TxOutItem, amount int64, sourceScript []byte, idx int, thorchainHeight int64) error {
func (c *Client) signUTXODOGE(redeemTx *dogewire.MsgTx, tx stypes.TxOutItem, amount int64, sourceScript []byte, idx int) error {
var signable dogetxscript.Signable
if tx.VaultPubKey.Equals(c.nodePubKey) {
signable = dogetxscript.NewPrivateKeySignable((*dogeec.PrivateKey)(c.nodePrivKey))
......