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 (2)
......@@ -78,6 +78,7 @@ type BlockScannerConfiguration struct {
EnforceBlockHeight bool `json:"enforce_block_height" mapstructure:"enforce_block_height"`
DBPath string `json:"db_path" mapstructure:"db_path"`
ChainID common.Chain `json:"chain_id" mapstructure:"chain_id"`
SuggestedFeeVersion int `json:"suggested_fee_version" mapstructure:"suggested_fee_version"`
}
// ClientConfiguration
......
......@@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"math/big"
"sort"
"strconv"
"strings"
......@@ -48,7 +49,6 @@ const (
decimalMethod = "decimals"
defaultDecimals = 18 // on ETH , consolidate all decimals to 18, in Wei
tenGwei = 10000000000
gasCacheBlocks = 20
)
// ETHScanner is a scanner that understand how to interact with ETH chain ,and scan block , parse smart contract etc
......@@ -75,6 +75,10 @@ type ETHScanner struct {
solvencyReporter SolvencyReporter
whitelistTokens []ERC20Token
signerCacheManager *signercache.CacheManager
// set at creation based on the SuggestedFeeVersion in config
gasCacheBlocks uint64
blockLag uint64
}
// NewETHScanner create a new instance of ETHScanner
......@@ -122,6 +126,20 @@ func NewETHScanner(cfg config.BlockScannerConfiguration,
return nil, fmt.Errorf("fail to load token list,err: %w", err)
}
// suggested gas fee configuration
var gasCacheBlocks, blockLag uint64
switch cfg.SuggestedFeeVersion {
case 1:
gasCacheBlocks = 20
blockLag = 0
case 2:
gasCacheBlocks = 40
blockLag = 1
default:
log.Fatal().Int("version", cfg.SuggestedFeeVersion).Msg("unsupported suggested fee version")
}
log.Info().Msgf("suggested fee version: %d", cfg.SuggestedFeeVersion)
return &ETHScanner{
cfg: cfg,
logger: log.Logger.With().Str("module", "block_scanner").Str("chain", common.ETHChain.String()).Logger(),
......@@ -143,6 +161,8 @@ func NewETHScanner(cfg config.BlockScannerConfiguration,
solvencyReporter: solvencyReporter,
whitelistTokens: whitelistTokens.Tokens,
signerCacheManager: signerCacheManager,
gasCacheBlocks: gasCacheBlocks,
blockLag: blockLag,
}, nil
}
......@@ -159,11 +179,11 @@ func (e *ETHScanner) getContext() (context.Context, context.CancelFunc) {
func (e *ETHScanner) GetHeight() (int64, error) {
ctx, cancel := e.getContext()
defer cancel()
block, err := e.client.BlockByNumber(ctx, nil)
height, err := e.client.BlockNumber(ctx)
if err != nil {
return -1, fmt.Errorf("fail to get block height: %w", err)
}
return block.Number().Int64(), nil
return int64(height - e.blockLag), nil
}
// FetchMemPool get tx from mempool
......@@ -204,27 +224,53 @@ func (e *ETHScanner) FetchTxs(height int64) (stypes.TxIn, error) {
}()
}
if e.gasPriceChanged {
// only send the network fee to THORNode when the price get changed
gasPrice := e.GetGasPrice() // gas price is in wei
// convert the gas price to 1E8 , the decimals used in thorchain
gasPriceForThorchain := big.NewInt(0).Div(gasPrice, big.NewInt(common.One*100))
gasValue := gasPriceForThorchain.Uint64()
if gasValue == 0 {
gasValue = 1
}
// make it to round up
if big.NewInt(1).Mul(big.NewInt(int64(gasValue)), big.NewInt(common.One*100)).Cmp(gasPrice) < 0 {
gasValue++
}
// only report the gas price when it actually get changed
if gasValue != e.lastReportedGasPrice {
e.lastReportedGasPrice = gasValue
if _, err := e.bridge.PostNetworkFee(height, common.ETHChain, MaxContractGas, gasValue); err != nil {
e.logger.Err(err).Msg("fail to post ETH chain single transfer fee to THORNode")
switch e.cfg.SuggestedFeeVersion {
case 1:
if e.gasPriceChanged {
// only send the network fee to THORNode when the price get changed
gasPrice := e.GetGasPrice() // gas price is in wei
// convert the gas price to 1E8 , the decimals used in thorchain
gasPriceForThorchain := big.NewInt(0).Div(gasPrice, big.NewInt(common.One*100))
gasValue := gasPriceForThorchain.Uint64()
if gasValue == 0 {
gasValue = 1
}
// make it to round up
if big.NewInt(1).Mul(big.NewInt(int64(gasValue)), big.NewInt(common.One*100)).Cmp(gasPrice) < 0 {
gasValue++
}
// only report the gas price when it actually get changed
if gasValue != e.lastReportedGasPrice {
e.lastReportedGasPrice = gasValue
if _, err := e.bridge.PostNetworkFee(height, common.ETHChain, MaxContractGas, gasValue); err != nil {
e.logger.Err(err).Msg("fail to post ETH chain single transfer fee to THORNode")
}
}
}
case 2:
gasPrice := e.GetGasPrice()
// skip posting if there is not yet a fee
if gasPrice.Cmp(big.NewInt(0)) == 0 {
break
}
// gas price to 1e8
tcGasPrice := new(big.Int).Div(gasPrice, big.NewInt(common.One*100)).Uint64()
// skip posting if the fee has not changed
if tcGasPrice == e.lastReportedGasPrice {
break
}
// post to thorchain
if _, err := e.bridge.PostNetworkFee(height, common.ETHChain, MaxContractGas, tcGasPrice); err != nil {
e.logger.Err(err).Msg("fail to post ETH chain single transfer fee to THORNode")
} else {
e.lastReportedGasPrice = tcGasPrice
}
}
if e.solvencyReporter != nil {
if err := e.solvencyReporter(height); err != nil {
e.logger.Err(err).Msg("fail to report Solvency info to THORNode")
......@@ -233,7 +279,18 @@ func (e *ETHScanner) FetchTxs(height int64) (stypes.TxIn, error) {
return txIn, nil
}
func (e *ETHScanner) updateGasPrice() {
// get the highest gas price in the last 50 blocks , make sure we can pay enough fee
func (e *ETHScanner) getHighestGasPrice() *big.Int {
gasPrice := big.NewInt(0)
for _, v := range e.gasCache {
if v.Cmp(gasPrice) > 0 {
gasPrice = v
}
}
return gasPrice
}
func (e *ETHScanner) updateGasPriceV1() {
ctx, cancel := e.getContext()
defer cancel()
gasPrice, err := e.client.SuggestGasPrice(ctx)
......@@ -249,6 +306,7 @@ func (e *ETHScanner) updateGasPrice() {
e.logger.Info().Msg("gas price is zero , not valid")
return
}
// make sure the gas price is at least ten Gwei
if gasPrice.Cmp(big.NewInt(tenGwei)) < 0 {
gasPrice = big.NewInt(tenGwei)
......@@ -257,8 +315,8 @@ func (e *ETHScanner) updateGasPrice() {
gasPrice = big.NewInt(1).Mul(gasPrice, big.NewInt(3))
gasPrice = big.NewInt(1).Div(gasPrice, big.NewInt(2))
e.gasCache = append(e.gasCache, gasPrice)
if len(e.gasCache) > gasCacheBlocks {
e.gasCache = e.gasCache[(len(e.gasCache) - gasCacheBlocks):]
if len(e.gasCache) > int(e.gasCacheBlocks) {
e.gasCache = e.gasCache[(len(e.gasCache) - int(e.gasCacheBlocks)):]
}
gasPrice = e.getHighestGasPrice()
if e.gasPrice.Cmp(gasPrice) == 0 {
......@@ -278,15 +336,51 @@ func (e *ETHScanner) updateGasPrice() {
e.m.GetCounter(metrics.GasPriceChange(common.ETHChain)).Inc()
}
// get the highest gas price in the last 50 blocks , make sure we can pay enough fee
func (e *ETHScanner) getHighestGasPrice() *big.Int {
gasPrice := big.NewInt(0)
for _, v := range e.gasCache {
if v.Cmp(gasPrice) > 0 {
gasPrice = v
}
func (e *ETHScanner) updateGasPriceV2(prices []*big.Int) {
// skip empty blocks
if len(prices) == 0 {
return
}
return gasPrice
// find the 25th percentile gas price in the block
sort.Slice(prices, func(i, j int) bool { return prices[i].Cmp(prices[j]) == -1 })
gasPrice := prices[len(prices)/4]
// add to the cache
e.gasCache = append(e.gasCache, gasPrice)
if len(e.gasCache) > int(e.gasCacheBlocks) {
e.gasCache = e.gasCache[(len(e.gasCache) - int(e.gasCacheBlocks)):]
}
// skip update unless cache is full
if len(e.gasCache) < int(e.gasCacheBlocks) {
return
}
// compute the mean of the 25th percentiles in the cache
sum := new(big.Int)
for _, fee := range e.gasCache {
sum.Add(sum, fee)
}
mean := new(big.Int).Quo(sum, big.NewInt(int64(e.gasCacheBlocks)))
// compute the standard deviation of the 25th percentiles in cache
std := new(big.Int)
for _, fee := range e.gasCache {
v := new(big.Int).Sub(fee, mean)
v.Mul(v, v)
std.Add(std, v)
}
std.Quo(std, big.NewInt(int64(e.gasCacheBlocks)))
std.Sqrt(std)
// mean + 3x standard deviation of the 25th percentile fee over blocks
e.gasPrice = mean.Add(mean, std.Mul(std, big.NewInt(3)))
// record metrics
gasPriceFloat, _ := new(big.Float).SetInt64(e.gasPrice.Int64()).Float64()
e.m.GetGauge(metrics.GasPrice(common.ETHChain)).Set(gasPriceFloat)
e.m.GetCounter(metrics.GasPriceChange(common.ETHChain)).Inc()
}
// processBlock extracts transactions from block
......@@ -301,7 +395,18 @@ func (e *ETHScanner) processBlock(block *etypes.Block) (stypes.TxIn, error) {
Finalised: false,
}
// Update gas price
e.updateGasPrice()
switch e.cfg.SuggestedFeeVersion {
case 1:
e.updateGasPriceV1()
case 2:
var txsGas []*big.Int
for _, tx := range block.Transactions() {
txsGas = append(txsGas, tx.GasPrice())
}
e.updateGasPriceV2(txsGas)
}
reorgedTxIns, err := e.processReorg(block.Header())
if err != nil {
e.logger.Error().Err(err).Msgf("fail to process reorg for block %d", height)
......
......@@ -70,6 +70,7 @@ func getConfigForTest(rpcHost string) config.BlockScannerConfiguration {
MaxHTTPRequestRetry: 3,
BlockHeightDiscoverBackoff: time.Second,
BlockRetryInterval: time.Second,
SuggestedFeeVersion: 1,
}
}
......@@ -602,3 +603,81 @@ func (s *BlockScannerTestSuite) TestProcessReOrg(c *C) {
c.Assert(err, IsNil)
c.Assert(blockMeta, NotNil)
}
// -------------------------------------------------------------------------------------
// GasPriceV2
// -------------------------------------------------------------------------------------
func (s *BlockScannerTestSuite) TestGasPriceV2(c *C) {
storage, err := blockscanner.NewBlockScannerStorage("")
c.Assert(err, IsNil)
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
body, err := ioutil.ReadAll(req.Body)
c.Assert(err, IsNil)
type RPCRequest struct {
JSONRPC string `json:"jsonrpc"`
ID interface{} `json:"id"`
Method string `json:"method"`
Params json.RawMessage `json:"params"`
}
var rpcRequest RPCRequest
err = json.Unmarshal(body, &rpcRequest)
c.Assert(err, IsNil)
if rpcRequest.Method == "eth_chainId" {
_, err := rw.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0x539"}`))
c.Assert(err, IsNil)
}
if rpcRequest.Method == "eth_gasPrice" {
_, err := rw.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0x1"}`))
c.Assert(err, IsNil)
}
}))
ethClient, err := ethclient.Dial(server.URL)
c.Assert(err, IsNil)
pubKeyManager, err := pubkeymanager.NewPubKeyManager(s.bridge, s.m)
c.Assert(err, IsNil)
solvencyReporter := func(height int64) error {
return nil
}
conf := getConfigForTest("127.0.0.1")
conf.SuggestedFeeVersion = 2
bs, err := NewETHScanner(conf, storage, big.NewInt(int64(types.Mainnet)), ethClient, s.bridge, s.m, pubKeyManager, solvencyReporter, nil)
c.Assert(err, IsNil)
c.Assert(bs, NotNil)
// almost fill gas cache
for i := 0; i < 39; i++ {
bs.updateGasPriceV2([]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3), big.NewInt(4)})
}
// empty blocks should not count
bs.updateGasPriceV2([]*big.Int{})
c.Assert(len(bs.gasCache), Equals, 39)
c.Assert(bs.gasPrice.Cmp(big.NewInt(0)), Equals, 0)
// now we should get the average of the 25th percentile gas (2)
bs.updateGasPriceV2([]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3), big.NewInt(4)})
c.Assert(len(bs.gasCache), Equals, 40)
c.Assert(bs.gasPrice.Uint64(), Equals, big.NewInt(2).Uint64())
// add 20 more blocks with 2x the 25th percentile and we should get 6 (3 + 3x stddev)
for i := 0; i < 20; i++ {
bs.updateGasPriceV2([]*big.Int{big.NewInt(2), big.NewInt(4), big.NewInt(6), big.NewInt(8)})
}
c.Assert(len(bs.gasCache), Equals, 40)
c.Assert(bs.gasPrice.Uint64(), Equals, big.NewInt(6).Uint64())
// add 20 more blocks with 2x the 25th percentile and we should get 4
for i := 0; i < 20; i++ {
bs.updateGasPriceV2([]*big.Int{big.NewInt(2), big.NewInt(4), big.NewInt(6), big.NewInt(8)})
}
c.Assert(len(bs.gasCache), Equals, 40)
c.Assert(bs.gasPrice.Uint64(), Equals, big.NewInt(4).Uint64())
// add 20 more blocks with 2x the 25th percentile and we should get 12 (6 + 3x stddev)
for i := 0; i < 20; i++ {
bs.updateGasPriceV2([]*big.Int{big.NewInt(4), big.NewInt(8), big.NewInt(12), big.NewInt(16)})
}
c.Assert(len(bs.gasCache), Equals, 40)
c.Assert(bs.gasPrice.Uint64(), Equals, big.NewInt(12).Uint64())
}
......@@ -153,6 +153,10 @@ func (s *EthereumSuite) SetUpTest(c *C) {
}}`))
c.Assert(err, IsNil)
}
if rpcRequest.Method == "eth_blockNumber" {
_, err := rw.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0x7"}`))
c.Assert(err, IsNil)
}
if rpcRequest.Method == "eth_getBlockByNumber" {
_, err := rw.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":{
"difficulty": "0x31962a3fc82b",
......@@ -245,8 +249,9 @@ func (s *EthereumSuite) TestConvertSigningAmount(c *C) {
e, err := NewClient(s.thorKeys, config.ChainConfiguration{
RPCHost: "http://" + s.server.Listener.Addr().String(),
BlockScanner: config.BlockScannerConfiguration{
StartBlockHeight: 1, // avoids querying thorchain for block height
HTTPRequestTimeout: time.Second,
StartBlockHeight: 1, // avoids querying thorchain for block height
HTTPRequestTimeout: time.Second,
SuggestedFeeVersion: 1,
},
}, nil, s.bridge, s.m, pubkeyMgr, poolMgr)
c.Assert(err, IsNil)
......@@ -278,8 +283,9 @@ func (s *EthereumSuite) TestClient(c *C) {
e2, err2 := NewClient(s.thorKeys, config.ChainConfiguration{
RPCHost: "http://" + s.server.Listener.Addr().String(),
BlockScanner: config.BlockScannerConfiguration{
StartBlockHeight: 1, // avoids querying thorchain for block height
HTTPRequestTimeout: time.Second,
StartBlockHeight: 1, // avoids querying thorchain for block height
HTTPRequestTimeout: time.Second,
SuggestedFeeVersion: 1,
},
}, nil, s.bridge, s.m, pubkeyMgr, poolMgr)
c.Assert(err2, IsNil)
......@@ -360,8 +366,9 @@ func (s *EthereumSuite) TestGetAccount(c *C) {
e, err := NewClient(s.thorKeys, config.ChainConfiguration{
RPCHost: "http://" + s.server.Listener.Addr().String(),
BlockScanner: config.BlockScannerConfiguration{
StartBlockHeight: 1, // avoids querying thorchain for block height
HTTPRequestTimeout: time.Second,
StartBlockHeight: 1, // avoids querying thorchain for block height
HTTPRequestTimeout: time.Second,
SuggestedFeeVersion: 1,
},
}, nil, s.bridge, s.m, pubkeyMgr, poolMgr)
c.Assert(err, IsNil)
......@@ -383,8 +390,9 @@ func (s *EthereumSuite) TestSignETHTx(c *C) {
e, err := NewClient(s.thorKeys, config.ChainConfiguration{
RPCHost: "http://" + s.server.Listener.Addr().String(),
BlockScanner: config.BlockScannerConfiguration{
StartBlockHeight: 1, // avoids querying thorchain for block height
HTTPRequestTimeout: time.Second,
StartBlockHeight: 1, // avoids querying thorchain for block height
HTTPRequestTimeout: time.Second,
SuggestedFeeVersion: 1,
},
}, nil, s.bridge, s.m, pubkeyMgr, poolMgr)
c.Assert(err, IsNil)
......@@ -609,8 +617,9 @@ func (s *EthereumSuite) TestGetAsgardAddresses(c *C) {
e, err := NewClient(s.thorKeys, config.ChainConfiguration{
RPCHost: "http://" + s.server.Listener.Addr().String(),
BlockScanner: config.BlockScannerConfiguration{
StartBlockHeight: 1, // avoids querying thorchain for block height
HTTPRequestTimeout: time.Second,
StartBlockHeight: 1, // avoids querying thorchain for block height
HTTPRequestTimeout: time.Second,
SuggestedFeeVersion: 1,
},
}, nil, s.bridge, s.m, pubkeyMgr, poolMgr)
c.Assert(err, IsNil)
......@@ -629,8 +638,9 @@ func (s *EthereumSuite) TestGetConfirmationCount(c *C) {
e, err := NewClient(s.thorKeys, config.ChainConfiguration{
RPCHost: "http://" + s.server.Listener.Addr().String(),
BlockScanner: config.BlockScannerConfiguration{
StartBlockHeight: 1, // avoids querying thorchain for block height
HTTPRequestTimeout: time.Second,
StartBlockHeight: 1, // avoids querying thorchain for block height
HTTPRequestTimeout: time.Second,
SuggestedFeeVersion: 1,
},
}, nil, s.bridge, s.m, pubkeyMgr, poolMgr)
c.Assert(err, IsNil)
......
......@@ -179,8 +179,9 @@ func (s *UnstuckTestSuite) TestUnstuckProcess(c *C) {
e, err := NewClient(s.thorKeys, config.ChainConfiguration{
RPCHost: "http://" + s.server.Listener.Addr().String(),
BlockScanner: config.BlockScannerConfiguration{
StartBlockHeight: 1, // avoids querying thorchain for block height
HTTPRequestTimeout: time.Second * 10,
StartBlockHeight: 1, // avoids querying thorchain for block height
HTTPRequestTimeout: time.Second * 10,
SuggestedFeeVersion: 1,
},
}, nil, s.bridge, s.m, pubkeyMgr, poolMgr)
c.Assert(err, IsNil)
......
......@@ -21,6 +21,7 @@ LTC_DISABLED="${LTC_DISABLED:=false}"
# Ethereum chain config
ETH_HOST="${ETH_HOST:=http://ethereum:8545}"
ETH_START_BLOCK_HEIGHT="${ETH_START_BLOCK_HEIGHT:=0}"
ETH_SUGGESTED_FEE_VERSION="${ETH_SUGGESTED_FEE_VERSION:=1}"
# Dogecoin chain config
DOGE_HOST="${DOGE_HOST:=dogecoin:18332}"
......@@ -252,7 +253,8 @@ echo "{
\"http_request_write_timeout\": \"30s\",
\"max_http_request_retry\": 10,
\"start_block_height\": $ETH_START_BLOCK_HEIGHT,
\"db_path\": \"$OBSERVER_PATH\"
\"db_path\": \"$OBSERVER_PATH\",
\"suggested_fee_version\": $ETH_SUGGESTED_FEE_VERSION
}
}
],
......