github.com/codingfuture/orig-energi3@v0.8.4/energi/api/migration.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  	"context"
    21  	"crypto/ecdsa"
    22  	"crypto/sha256"
    23  	"errors"
    24  	"io"
    25  	"math/big"
    26  	"os"
    27  	"strings"
    28  
    29  	"github.com/ethereum/go-ethereum/accounts"
    30  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    31  	"github.com/ethereum/go-ethereum/accounts/keystore"
    32  	"github.com/ethereum/go-ethereum/common"
    33  	"github.com/ethereum/go-ethereum/common/hexutil"
    34  	"github.com/ethereum/go-ethereum/crypto"
    35  	"github.com/ethereum/go-ethereum/log"
    36  	"github.com/ethereum/go-ethereum/rpc"
    37  
    38  	"github.com/shengdoushi/base58"
    39  	"golang.org/x/crypto/ripemd160"
    40  
    41  	energi_abi "energi.world/core/gen3/energi/abi"
    42  	energi_common "energi.world/core/gen3/energi/common"
    43  	energi_params "energi.world/core/gen3/energi/params"
    44  )
    45  
    46  const (
    47  	base54PrivateKeyLen int    = 52
    48  	privateKeyLen       int    = 32
    49  	migrationGas        uint64 = 100000
    50  	ownerSafetyLimit    int    = 10000
    51  )
    52  
    53  type MigrationAPI struct {
    54  	backend    Backend
    55  	coinsCache *energi_common.CacheStorage
    56  
    57  	lastCoins   interface{}
    58  	lastBalance *big.Int
    59  }
    60  
    61  func NewMigrationAPI(b Backend) *MigrationAPI {
    62  	r := &MigrationAPI{
    63  		backend:    b,
    64  		coinsCache: energi_common.NewCacheStorage(),
    65  	}
    66  	b.OnSyncedHeadUpdates(func() {
    67  		r.listGen2Coins()
    68  	})
    69  	return r
    70  }
    71  
    72  type Gen2Coin struct {
    73  	ItemID   uint64
    74  	RawOwner common.Address
    75  	Owner    string
    76  	Amount   *hexutil.Big
    77  }
    78  
    79  type Gen2Key struct {
    80  	RawOwner common.Address
    81  	Key      *ecdsa.PrivateKey
    82  }
    83  
    84  func (m *MigrationAPI) ListGen2Coins() (coins []Gen2Coin, err error) {
    85  	if m.backend.IsPublicService() {
    86  		return nil, errors.New("This API is disabled for security reasons")
    87  	}
    88  
    89  	return m.listGen2Coins()
    90  }
    91  
    92  func (m *MigrationAPI) listGen2Coins() (coins []Gen2Coin, err error) {
    93  	data, err := m.coinsCache.Get(m.backend, m.listGen2CoinsUncached)
    94  	if err != nil || data == nil {
    95  		log.Error("listGen2Coins failed", "err", err)
    96  		return
    97  	}
    98  
    99  	coins = data.([]Gen2Coin)
   100  
   101  	return
   102  }
   103  
   104  func (m *MigrationAPI) listGen2CoinsUncached(num *big.Int) (interface{}, error) {
   105  	// Check if it makes sense to re-create the list at all
   106  	//---
   107  	state, _, err := m.backend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(num.Int64()))
   108  	if err != nil {
   109  		log.Error("Failed to get migration state", "err", err)
   110  		return nil, err
   111  	}
   112  
   113  	currBalance := state.GetBalance(energi_params.Energi_MigrationContract)
   114  
   115  	if m.lastCoins != nil && m.lastBalance.Cmp(currBalance) == 0 {
   116  		return m.lastCoins, nil
   117  	}
   118  
   119  	//---
   120  	log.Info("Preparing a new migration coin list")
   121  
   122  	mgrt_contract, err := energi_abi.NewGen2MigrationCaller(
   123  		energi_params.Energi_MigrationContract, m.backend.(bind.ContractCaller))
   124  	if err != nil {
   125  		log.Error("Failed to create contract face", "err", err)
   126  		return nil, err
   127  	}
   128  
   129  	call_opts := &bind.CallOpts{
   130  		BlockNumber: num,
   131  		GasLimit:    energi_params.UnlimitedGas,
   132  	}
   133  	bigItems, err := mgrt_contract.ItemCount(call_opts)
   134  	if err != nil {
   135  		log.Error("Failed to get coin count", "err", err)
   136  		return nil, err
   137  	}
   138  
   139  	items := bigItems.Int64()
   140  	coins := make([]Gen2Coin, 0, items)
   141  
   142  	prefix := byte(33)
   143  	if m.backend.ChainConfig().ChainID.Int64() == 49797 {
   144  		prefix = byte(127)
   145  	}
   146  
   147  	for i := int64(0); i < items; i++ {
   148  		res, err := mgrt_contract.Coins(call_opts, big.NewInt(i))
   149  		if err != nil {
   150  			log.Error("Failed to get coin info", "err", err)
   151  			return nil, err
   152  		}
   153  
   154  		owner := make([]byte, 25)
   155  		owner[0] = prefix
   156  		copy(owner[1:], res.Owner[:])
   157  		ownerhash := sha256.Sum256(owner[:21])
   158  		ownerhash = sha256.Sum256(ownerhash[:])
   159  		copy(owner[21:], ownerhash[:4])
   160  
   161  		coins = append(coins, Gen2Coin{
   162  			ItemID:   uint64(i),
   163  			RawOwner: common.BytesToAddress(res.Owner[:]),
   164  			Owner:    base58.Encode(owner, base58.BitcoinAlphabet),
   165  			Amount:   (*hexutil.Big)(res.Amount),
   166  		})
   167  	}
   168  
   169  	m.lastBalance = currBalance
   170  	m.lastCoins = coins
   171  
   172  	return coins, nil
   173  }
   174  
   175  func (m *MigrationAPI) SearchGen2Coins(
   176  	owners []string,
   177  	include_empty bool,
   178  ) (coins []Gen2Coin, err error) {
   179  	if m.backend.IsPublicService() && len(owners) > ownerSafetyLimit {
   180  		return nil, errors.New("Too many owners requests.")
   181  	}
   182  
   183  	rawOwners := make([]common.Address, len(owners))
   184  	for i, o := range owners {
   185  		ro, err := base58.Decode(o, base58.BitcoinAlphabet)
   186  		if err != nil || len(ro) < 20 {
   187  			log.Error("Failed to decode owner", "err", err, "owner", o)
   188  			continue
   189  		}
   190  		rawOwners[i] = common.BytesToAddress(ro[1 : len(ro)-4])
   191  	}
   192  	return m.searchGen2Coins(rawOwners, m.listGen2Coins, include_empty)
   193  }
   194  
   195  func (m *MigrationAPI) SearchRawGen2Coins(
   196  	rawOwners []common.Address,
   197  	include_empty bool,
   198  ) (coins []Gen2Coin, err error) {
   199  	if m.backend.IsPublicService() && len(rawOwners) > ownerSafetyLimit {
   200  		return nil, errors.New("Too many owners requests.")
   201  	}
   202  
   203  	return m.searchGen2Coins(rawOwners, m.listGen2Coins, include_empty)
   204  }
   205  
   206  type listCoins func() (coins []Gen2Coin, err error)
   207  
   208  func (m *MigrationAPI) searchGen2Coins(
   209  	owners []common.Address,
   210  	all_coins listCoins,
   211  	include_empty bool,
   212  ) (coins []Gen2Coin, err error) {
   213  	coins = make([]Gen2Coin, 0, len(owners))
   214  
   215  	owners_map := make(map[common.Address]bool)
   216  	for _, o := range owners {
   217  		owners_map[o] = true
   218  	}
   219  
   220  	list, err := all_coins()
   221  	if err != nil {
   222  		log.Error("Failed to get all coins", "err", err)
   223  		return
   224  	}
   225  
   226  	for _, c := range list {
   227  		if _, ok := owners_map[c.RawOwner]; ok {
   228  			if include_empty || c.Amount.ToInt().Cmp(common.Big0) > 0 {
   229  				coins = append(coins, c)
   230  			}
   231  		}
   232  	}
   233  
   234  	return coins, nil
   235  }
   236  
   237  func (m *MigrationAPI) loadGen2Dump(file string) (keys []Gen2Key, err error) {
   238  	f, err := os.Open(file)
   239  	if err != nil {
   240  		log.Error("Failed to open dump file", "err", err)
   241  		return nil, err
   242  	}
   243  	defer f.Close()
   244  
   245  	fi, err := f.Stat()
   246  	if err != nil {
   247  		log.Error("Failed to stat file", "err", err)
   248  		return nil, err
   249  	}
   250  
   251  	buf := make([]byte, fi.Size())
   252  	len, err := io.ReadFull(f, buf)
   253  	if err != nil {
   254  		log.Error("Failed to read file", "err", err)
   255  		return nil, err
   256  	}
   257  
   258  	return m.parseGen2Dump(string(buf[:len])), nil
   259  }
   260  
   261  func (m *MigrationAPI) parseGen2Dump(data string) (keys []Gen2Key) {
   262  	lines := strings.Split(data, "\n")
   263  	keys = make([]Gen2Key, 0, len(lines))
   264  
   265  	for i, l := range lines {
   266  		lp := strings.Split(l, " ")
   267  		if len(lp) < 3 || lp[0] == "#" {
   268  			continue
   269  		}
   270  
   271  		key, err := m.parseGen2Key(lp[0])
   272  		if err != nil {
   273  			log.Error("Failed to parse key", "err", err, "line", i)
   274  			continue
   275  		}
   276  
   277  		keys = append(keys, *key)
   278  	}
   279  
   280  	return
   281  }
   282  
   283  func (m *MigrationAPI) parseGen2Key(tkey string) (*Gen2Key, error) {
   284  	if len(tkey) != base54PrivateKeyLen {
   285  		return nil, errors.New("Invalid private key length")
   286  	}
   287  
   288  	rkey, err := base58.Decode(tkey, base58.BitcoinAlphabet)
   289  	if err != nil {
   290  		return nil, err
   291  	}
   292  
   293  	// There is prefix + key + [magic +] checksum
   294  	key_obj, err := crypto.ToECDSA(rkey[1 : 1+privateKeyLen])
   295  	if err != nil {
   296  		return nil, err
   297  	}
   298  
   299  	var owner common.Address
   300  
   301  	basehash := sha256.Sum256(crypto.CompressPubkey(&key_obj.PublicKey))
   302  	ripemd := ripemd160.New()
   303  	ripemd.Write(basehash[:])
   304  	owner.SetBytes(ripemd.Sum(nil))
   305  
   306  	return &Gen2Key{
   307  		RawOwner: owner,
   308  		Key:      key_obj,
   309  	}, nil
   310  }
   311  
   312  func (m *MigrationAPI) ClaimGen2CoinsDirect(
   313  	password *string,
   314  	dst common.Address,
   315  	tkey string,
   316  ) (txhash common.Hash, err error) {
   317  	key, err := m.parseGen2Key(tkey)
   318  	if err != nil {
   319  		log.Error("Failed to parse key", "err", err)
   320  		return
   321  	}
   322  
   323  	coins, err := m.SearchRawGen2Coins([]common.Address{key.RawOwner}, false)
   324  	if err != nil {
   325  		return
   326  	}
   327  
   328  	if len(coins) != 1 {
   329  		log.Error("Unable to find coins")
   330  		err = errors.New("No coins found")
   331  		return
   332  	}
   333  
   334  	txhash, err = m.claimGen2Coins(password, dst, &coins[0], key)
   335  	if err != nil {
   336  		log.Error("Failed to claim", "err", err)
   337  	}
   338  
   339  	return
   340  }
   341  
   342  func (m *MigrationAPI) ClaimGen2CoinsCombined(
   343  	password *string,
   344  	dst common.Address,
   345  	file string,
   346  ) (txhashes []common.Hash, err error) {
   347  	keys, err := m.loadGen2Dump(file)
   348  	if err != nil {
   349  		return nil, err
   350  	}
   351  
   352  	raw_owners := make([]common.Address, len(keys))
   353  	owner2key := make(map[common.Address]*Gen2Key, len(keys))
   354  	for i, k := range keys {
   355  		raw_owners[i] = k.RawOwner
   356  		owner2key[k.RawOwner] = &keys[i]
   357  	}
   358  
   359  	coins, err := m.SearchRawGen2Coins(raw_owners, false)
   360  	if err != nil {
   361  		return
   362  	}
   363  
   364  	txhashes = make([]common.Hash, len(coins))
   365  	for _, c := range coins {
   366  		txhash, err := m.claimGen2Coins(password, dst, &c, owner2key[c.RawOwner])
   367  		if err != nil {
   368  			return nil, err
   369  		}
   370  
   371  		txhashes = append(txhashes, txhash)
   372  	}
   373  
   374  	return txhashes, nil
   375  }
   376  
   377  func (m *MigrationAPI) ClaimGen2CoinsImport(
   378  	password string,
   379  	file string,
   380  ) (txhashes []common.Hash, err error) {
   381  	keys, err := m.loadGen2Dump(file)
   382  	if err != nil {
   383  		return
   384  	}
   385  
   386  	raw_owners := make([]common.Address, len(keys))
   387  	owner2key := make(map[common.Address]*Gen2Key, len(keys))
   388  	for i, k := range keys {
   389  		raw_owners[i] = k.RawOwner
   390  		owner2key[k.RawOwner] = &keys[i]
   391  	}
   392  
   393  	coins, err := m.SearchRawGen2Coins(raw_owners, false)
   394  	if err != nil {
   395  		return
   396  	}
   397  
   398  	am := m.backend.AccountManager()
   399  	ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
   400  
   401  	txhashes = make([]common.Hash, len(coins))
   402  	for _, c := range coins {
   403  		key := owner2key[c.RawOwner]
   404  		dst := crypto.PubkeyToAddress(key.Key.PublicKey)
   405  
   406  		//----
   407  		sink := make(chan accounts.WalletEvent)
   408  		evtsub := am.Subscribe(sink)
   409  		defer evtsub.Unsubscribe()
   410  
   411  		if _, err := ks.ImportECDSA(key.Key, password); err != nil {
   412  			log.Warn("Failed to import private key", "err", err)
   413  			// Most likely key exists
   414  		} else {
   415  			select {
   416  			case <-sink:
   417  			}
   418  		}
   419  
   420  		evtsub.Unsubscribe()
   421  		//----
   422  
   423  		txhash, err := m.claimGen2Coins(&password, dst, &c, key)
   424  		if err != nil {
   425  			return nil, err
   426  		}
   427  
   428  		txhashes = append(txhashes, txhash)
   429  	}
   430  
   431  	return txhashes, nil
   432  }
   433  
   434  func (m *MigrationAPI) claimGen2Coins(
   435  	password *string,
   436  	dst common.Address,
   437  	coin *Gen2Coin,
   438  	key *Gen2Key,
   439  ) (txhash common.Hash, err error) {
   440  	mgrt_contract_obj, err := energi_abi.NewGen2Migration(
   441  		energi_params.Energi_MigrationContract, m.backend.(bind.ContractBackend))
   442  	if err != nil {
   443  		return
   444  	}
   445  
   446  	mgrt_contract := energi_abi.Gen2MigrationSession{
   447  		Contract: mgrt_contract_obj,
   448  		CallOpts: bind.CallOpts{
   449  			From:     dst,
   450  			GasLimit: energi_params.UnlimitedGas,
   451  		},
   452  		TransactOpts: bind.TransactOpts{
   453  			From:     dst,
   454  			Signer:   createSignerCallback(m.backend, password),
   455  			Value:    common.Big0,
   456  			GasPrice: common.Big0,
   457  			GasLimit: migrationGas,
   458  		},
   459  	}
   460  
   461  	hts, err := mgrt_contract.HashToSign(dst)
   462  	if err != nil {
   463  		return
   464  	}
   465  
   466  	sig, err := crypto.Sign(hts[:], key.Key)
   467  	if err != nil {
   468  		return
   469  	}
   470  
   471  	if len(sig) != 65 {
   472  		err = errors.New("Wrong signature size")
   473  		return
   474  	}
   475  
   476  	item := new(big.Int).SetUint64(coin.ItemID)
   477  	r := [32]byte{}
   478  	copy(r[:], sig[:32])
   479  	s := [32]byte{}
   480  	copy(s[:], sig[32:64])
   481  	v := uint8(sig[64])
   482  
   483  	amt, err := mgrt_contract.VerifyClaim(item, dst, v, r, s)
   484  	if err != nil {
   485  		return
   486  	}
   487  
   488  	if amt.Cmp(common.Big0) == 0 {
   489  		log.Warn("Already claimed", "coins", coin.Owner)
   490  		return
   491  	}
   492  
   493  	tx, err := mgrt_contract.Claim(item, dst, v, r, s)
   494  	if tx != nil {
   495  		txhash = tx.Hash()
   496  		log.Info("Sent migration transaction", "tx", tx.Hash(), "coins", coin.Owner)
   497  	}
   498  
   499  	return
   500  }