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 }