github.com/codingfuture/orig-energi3@v0.8.4/energi/api/masternode.go (about)

     1  // Copyright 2019 The Energi Core Authors
     2  // This file is part of the Energi Core library.
     3  //
     4  // The Energi Core library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The Energi Core library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the Energi Core library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package api
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"math/big"
    23  
    24  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    25  	"github.com/ethereum/go-ethereum/common"
    26  	"github.com/ethereum/go-ethereum/common/hexutil"
    27  	"github.com/ethereum/go-ethereum/crypto"
    28  	"github.com/ethereum/go-ethereum/log"
    29  	"github.com/ethereum/go-ethereum/p2p/enode"
    30  
    31  	energi_abi "energi.world/core/gen3/energi/abi"
    32  	energi_common "energi.world/core/gen3/energi/common"
    33  	energi_params "energi.world/core/gen3/energi/params"
    34  )
    35  
    36  const (
    37  	mntokenCallGas    uint64 = energi_params.MasternodeCallGas
    38  	masternodeCallGas uint64 = energi_params.MasternodeCallGas
    39  )
    40  
    41  type MasternodeAPI struct {
    42  	backend    Backend
    43  	nodesCache *energi_common.CacheStorage
    44  	statsCache *energi_common.CacheStorage
    45  }
    46  
    47  func NewMasternodeAPI(b Backend) *MasternodeAPI {
    48  	r := &MasternodeAPI{
    49  		backend:    b,
    50  		nodesCache: energi_common.NewCacheStorage(),
    51  		statsCache: energi_common.NewCacheStorage(),
    52  	}
    53  	b.OnSyncedHeadUpdates(func() {
    54  		r.ListMasternodes()
    55  		r.Stats()
    56  	})
    57  	return r
    58  }
    59  
    60  type MasternodeStats struct {
    61  	Active           uint64
    62  	Total            uint64
    63  	ActiveCollateral *hexutil.Big
    64  	TotalCollateral  *hexutil.Big
    65  	MaxOfAllTimes    *hexutil.Big
    66  }
    67  
    68  func (m *MasternodeAPI) token(
    69  	password *string,
    70  	dst common.Address,
    71  ) (session *energi_abi.IMasternodeTokenSession, err error) {
    72  	contract, err := energi_abi.NewIMasternodeToken(
    73  		energi_params.Energi_MasternodeToken, m.backend.(bind.ContractBackend))
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	session = &energi_abi.IMasternodeTokenSession{
    79  		Contract: contract,
    80  		CallOpts: bind.CallOpts{
    81  			Pending:  true,
    82  			From:     dst,
    83  			GasLimit: energi_params.UnlimitedGas,
    84  		},
    85  		TransactOpts: bind.TransactOpts{
    86  			From:     dst,
    87  			Signer:   createSignerCallback(m.backend, password),
    88  			Value:    common.Big0,
    89  			GasLimit: mntokenCallGas,
    90  		},
    91  	}
    92  	return
    93  }
    94  
    95  func (m *MasternodeAPI) CollateralBalance(
    96  	dst common.Address,
    97  ) (ret struct {
    98  	Balance   *hexutil.Big
    99  	LastBlock *hexutil.Big
   100  }, err error) {
   101  	token, err := energi_abi.NewIMasternodeTokenCaller(
   102  		energi_params.Energi_MasternodeToken, m.backend.(bind.ContractCaller))
   103  	if err != nil {
   104  		log.Error("Failed", "err", err)
   105  		return ret, err
   106  	}
   107  
   108  	res, err := token.BalanceInfo(
   109  		&bind.CallOpts{
   110  			From:     dst,
   111  			GasLimit: energi_params.UnlimitedGas,
   112  		},
   113  		dst,
   114  	)
   115  	if err != nil {
   116  		log.Error("Failed", "err", err)
   117  		return ret, err
   118  	}
   119  
   120  	ret.Balance = (*hexutil.Big)(res.Balance)
   121  	ret.LastBlock = (*hexutil.Big)(res.LastBlock)
   122  	return ret, nil
   123  }
   124  
   125  func (m *MasternodeAPI) DepositCollateral(
   126  	dst common.Address,
   127  	amount *hexutil.Big,
   128  	password *string,
   129  ) (txhash common.Hash, err error) {
   130  	registry, err := masternodeRegistry(password, dst, m.backend)
   131  	if err != nil {
   132  		return
   133  	}
   134  
   135  	limits, err := registry.CollateralLimits()
   136  	if err != nil {
   137  		return
   138  	}
   139  
   140  	if err = m.validateAmount("Deposit", amount.ToInt(), limits.Min); err != nil {
   141  		return
   142  	}
   143  
   144  	token, err := m.token(password, dst)
   145  	if err != nil {
   146  		return
   147  	}
   148  
   149  	balance, err := token.BalanceOf(dst)
   150  	if err != nil {
   151  		// Possible new masternode detected.
   152  		log.Warn("Fetching masternode collateral failed: %v", err)
   153  	}
   154  
   155  	newTotalAmount := new(big.Int).Add(balance, amount.ToInt())
   156  
   157  	// Expected total amount should not be more than the maximum collateral value allowed.
   158  	if newTotalAmount.Cmp(limits.Max) > 0 {
   159  		err = fmt.Errorf("Total expected deposits should not exceed max allowed deposit")
   160  		return
   161  	}
   162  
   163  	token.TransactOpts.Value = amount.ToInt()
   164  	tx, err := token.DepositCollateral()
   165  
   166  	if tx != nil {
   167  		txhash = tx.Hash()
   168  		log.Info("Note: please wait until the collateral TX gets into a block!", "tx", txhash.Hex())
   169  	}
   170  
   171  	return
   172  }
   173  
   174  func (m *MasternodeAPI) WithdrawCollateral(
   175  	dst common.Address,
   176  	amount *hexutil.Big,
   177  	password *string,
   178  ) (txhash common.Hash, err error) {
   179  	registry, err := masternodeRegistry(password, dst, m.backend)
   180  	if err != nil {
   181  		return
   182  	}
   183  
   184  	limits, err := registry.CollateralLimits()
   185  	if err != nil {
   186  		return
   187  	}
   188  
   189  	if err = m.validateAmount("Withdrawal", amount.ToInt(), limits.Min); err != nil {
   190  		return
   191  	}
   192  
   193  	token, err := m.token(password, dst)
   194  	if err != nil {
   195  		return
   196  	}
   197  
   198  	balance, err := token.BalanceOf(dst)
   199  	if err != nil {
   200  		err = fmt.Errorf("Fetching masternode collateral failed: %v", err)
   201  		return
   202  	}
   203  
   204  	// Amount to withdraw should be less than or equal to the collateral amount.
   205  	if amount.ToInt().Cmp(balance) > 0 {
   206  		err = fmt.Errorf("Withdrawal amount is greater than the collateral balance amount")
   207  		return
   208  	}
   209  
   210  	tx, err := token.WithdrawCollateral(amount.ToInt())
   211  
   212  	if tx != nil {
   213  		txhash = tx.Hash()
   214  		log.Info("Note: please wait until the collateral TX gets into a block!", "tx", txhash.Hex())
   215  	}
   216  
   217  	return
   218  }
   219  
   220  func (m *MasternodeAPI) validateAmount(validateType string, amount, minColl *big.Int) error {
   221  	// Amount to should be greater than zero.
   222  	if amount.Cmp(common.Big0) < 1 {
   223  		return fmt.Errorf("%v amount should be greater than zero", validateType)
   224  	}
   225  
   226  	// Amount should be a multiple of the minimum collateral amount allowed.
   227  	if new(big.Int).Mod(amount, minColl).Cmp(common.Big0) != 0 {
   228  		return fmt.Errorf("%v amount should be a multiple of the minimum collateral amount", validateType)
   229  	}
   230  
   231  	return nil
   232  }
   233  
   234  type MNInfo struct {
   235  	Masternode     common.Address
   236  	Owner          common.Address
   237  	Enode          string
   238  	Collateral     *hexutil.Big
   239  	AnnouncedBlock uint64
   240  	IsActive       bool
   241  	IsAlive        bool
   242  	SWFeatures     *hexutil.Big
   243  	SWVersion      string
   244  }
   245  
   246  func (m *MasternodeAPI) ListMasternodes() (res []MNInfo, err error) {
   247  	data, err := m.nodesCache.Get(m.backend, m.listMasternodes)
   248  	if err != nil || data == nil {
   249  		log.Error("ListMasternodes failed", "err", err)
   250  		return
   251  	}
   252  
   253  	res = data.([]MNInfo)
   254  
   255  	return
   256  }
   257  
   258  func (m *MasternodeAPI) listMasternodes(num *big.Int) (interface{}, error) {
   259  	registry, err := energi_abi.NewIMasternodeRegistryV2Caller(
   260  		energi_params.Energi_MasternodeRegistry, m.backend.(bind.ContractCaller))
   261  	if err != nil {
   262  		log.Error("Failed", "err", err)
   263  		return nil, err
   264  	}
   265  
   266  	call_opts := &bind.CallOpts{
   267  		BlockNumber: num,
   268  		GasLimit:    energi_params.UnlimitedGas,
   269  	}
   270  	masternodes, err := registry.Enumerate(call_opts)
   271  	if err != nil {
   272  		log.Error("Failed", "err", err)
   273  		return nil, err
   274  	}
   275  
   276  	res := make([]MNInfo, 0, len(masternodes))
   277  	for _, mn := range masternodes {
   278  		mninfo, err := registry.Info(call_opts, mn)
   279  		if err != nil {
   280  			log.Warn("Info error", "mn", mn, "err", err)
   281  			continue
   282  		}
   283  
   284  		isActive, err := registry.IsActive(call_opts, mn)
   285  		if err != nil {
   286  			log.Warn("IsActive error", "mn", mn, "err", err)
   287  			continue
   288  		}
   289  
   290  		canHeartbeat, err := registry.CanHeartbeat(call_opts, mn)
   291  		if err != nil {
   292  			log.Warn("CanHeartbeat error", "mn", mn, "err", err)
   293  			continue
   294  		}
   295  
   296  		res = append(res, MNInfo{
   297  			Masternode:     mn,
   298  			Owner:          mninfo.Owner,
   299  			Enode:          m.enode(mninfo.Ipv4address, mninfo.Enode),
   300  			Collateral:     (*hexutil.Big)(mninfo.Collateral),
   301  			AnnouncedBlock: mninfo.AnnouncedBlock.Uint64(),
   302  			IsActive:       isActive,
   303  			IsAlive:        isActive && !canHeartbeat,
   304  			SWFeatures:     (*hexutil.Big)(mninfo.SwFeatures),
   305  			SWVersion:      energi_common.SWVersionIntToString(mninfo.SwFeatures),
   306  		})
   307  	}
   308  
   309  	return res, err
   310  }
   311  
   312  func (m *MasternodeAPI) MasternodeInfo(owner_or_mn common.Address) (res MNInfo, err error) {
   313  	Mns, err := m.ListMasternodes()
   314  	if err != nil {
   315  		log.Error("Failed at m.ListMasternodes", "err", err)
   316  		return
   317  	}
   318  
   319  	for _, node := range Mns {
   320  		if node.Masternode == owner_or_mn || node.Owner == owner_or_mn {
   321  			res = node
   322  			break
   323  		}
   324  	}
   325  
   326  	return
   327  }
   328  
   329  func (m *MasternodeAPI) Stats() (res *MasternodeStats, err error) {
   330  	data, err := m.statsCache.Get(m.backend, m.stats)
   331  
   332  	if err != nil || data == nil {
   333  		log.Error("Stats failed", "err", err)
   334  		return
   335  	}
   336  
   337  	res = data.(*MasternodeStats)
   338  	return
   339  }
   340  
   341  func (m *MasternodeAPI) stats(num *big.Int) (interface{}, error) {
   342  	registry, err := energi_abi.NewIMasternodeRegistryV2Caller(
   343  		energi_params.Energi_MasternodeRegistry, m.backend.(bind.ContractCaller))
   344  	if err != nil {
   345  		log.Error("Failed", "err", err)
   346  		return nil, err
   347  	}
   348  
   349  	call_opts := &bind.CallOpts{
   350  		BlockNumber: num,
   351  		GasLimit:    energi_params.UnlimitedGas,
   352  	}
   353  	count, err := registry.Count(call_opts)
   354  	if err != nil {
   355  		log.Error("Failed", "err", err)
   356  		return nil, err
   357  	}
   358  
   359  	res := &MasternodeStats{}
   360  	res.Active = count.Active.Uint64()
   361  	res.Total = count.Total.Uint64()
   362  	res.ActiveCollateral = (*hexutil.Big)(count.ActiveCollateral)
   363  	res.TotalCollateral = (*hexutil.Big)(count.TotalCollateral)
   364  	res.MaxOfAllTimes = (*hexutil.Big)(count.MaxOfAllTimes)
   365  
   366  	return res, nil
   367  }
   368  
   369  func (m *MasternodeAPI) enode(ipv4address uint32, pubkey [2][32]byte) string {
   370  	cfg := m.backend.ChainConfig()
   371  	res := energi_common.MastenodeEnode(ipv4address, pubkey, cfg)
   372  
   373  	if res == nil {
   374  		return ""
   375  	}
   376  
   377  	return res.String()
   378  }
   379  
   380  func masternodeRegistry(
   381  	password *string,
   382  	dst common.Address,
   383  	backend Backend,
   384  ) (session *energi_abi.IMasternodeRegistryV2Session, err error) {
   385  	contract, err := energi_abi.NewIMasternodeRegistryV2(
   386  		energi_params.Energi_MasternodeRegistry, backend.(bind.ContractBackend))
   387  	if err != nil {
   388  		return nil, err
   389  	}
   390  
   391  	session = &energi_abi.IMasternodeRegistryV2Session{
   392  		Contract: contract,
   393  		CallOpts: bind.CallOpts{
   394  			From:     dst,
   395  			GasLimit: energi_params.UnlimitedGas,
   396  		},
   397  		TransactOpts: bind.TransactOpts{
   398  			From:     dst,
   399  			Signer:   createSignerCallback(backend, password),
   400  			Value:    common.Big0,
   401  			GasLimit: masternodeCallGas,
   402  		},
   403  	}
   404  	return
   405  }
   406  
   407  func (m *MasternodeAPI) Announce(
   408  	owner common.Address,
   409  	enode_url string,
   410  	password *string,
   411  ) (txhash common.Hash, err error) {
   412  	registry, err := masternodeRegistry(password, owner, m.backend)
   413  	if err != nil {
   414  		return
   415  	}
   416  
   417  	var (
   418  		ipv4address uint32
   419  		pubkey      [2][32]byte
   420  	)
   421  
   422  	//---
   423  	res, err := enode.ParseV4(enode_url)
   424  	if err != nil {
   425  		return
   426  	}
   427  
   428  	//---
   429  	ip := res.IP().To4()
   430  	if ip == nil {
   431  		err = errors.New("Invalid IPv4")
   432  		return
   433  	}
   434  
   435  	if ip[0] == byte(127) || ip[0] == byte(10) ||
   436  		(ip[0] == byte(192) && ip[1] == byte(168)) ||
   437  		(ip[0] == byte(172) && (ip[1]&0xF0) == byte(16)) {
   438  		err = errors.New("Wrong enode IP")
   439  		return
   440  	}
   441  
   442  	cfg := m.backend.ChainConfig()
   443  
   444  	if res.UDP() != int(cfg.ChainID.Int64()) || res.TCP() != int(cfg.ChainID.Int64()) {
   445  		err = errors.New("Wrong enode port")
   446  		return
   447  	}
   448  
   449  	ipv4address = uint32(ip[0])<<24 | uint32(ip[1])<<16 | uint32(ip[2])<<8 | uint32(ip[3])
   450  
   451  	//---
   452  	pk := crypto.CompressPubkey(res.Pubkey())
   453  	if len(pk) != 33 {
   454  		log.Error("Wrong public key length", "pklen", len(pk))
   455  		err = errors.New("Wrong public key")
   456  		return
   457  	}
   458  
   459  	copy(pubkey[0][:], pk[:32])
   460  	copy(pubkey[1][:], pk[32:33])
   461  
   462  	//---
   463  	masternode := crypto.PubkeyToAddress(*res.Pubkey())
   464  
   465  	//---
   466  	tx, err := registry.Announce(masternode, ipv4address, pubkey)
   467  
   468  	if tx != nil {
   469  		txhash = tx.Hash()
   470  		log.Info("Note: please wait until the TX gets into a block!", "tx", txhash.Hex())
   471  	}
   472  
   473  	return
   474  }
   475  
   476  func (m *MasternodeAPI) Denounce(
   477  	owner common.Address,
   478  	password *string,
   479  ) (txhash common.Hash, err error) {
   480  	registry, err := masternodeRegistry(password, owner, m.backend)
   481  	if err != nil {
   482  		return
   483  	}
   484  
   485  	ownerinfo, err := registry.OwnerInfo(owner)
   486  	if err != nil {
   487  		log.Error("Not found", "owner", owner)
   488  		return
   489  	}
   490  
   491  	tx, err := registry.Denounce(ownerinfo.Masternode)
   492  
   493  	if tx != nil {
   494  		txhash = tx.Hash()
   495  		log.Info("Note: please wait until the TX gets into a block!", "tx", txhash.Hex())
   496  	}
   497  
   498  	return
   499  }