// Copyright (c) 2018 XDPoSChain // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with this program. If not, see . package contracts import ( "bytes" "crypto/aes" "crypto/cipher" cryptoRand "crypto/rand" "encoding/base64" "encoding/json" "fmt" "io" "math/big" "math/rand" "strconv" "sync" "time" "github.com/XinFinOrg/XDPoSChain/accounts" "github.com/XinFinOrg/XDPoSChain/accounts/abi/bind" "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common/hexutil" "github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/contracts/blocksigner/contract" randomizeContract "github.com/XinFinOrg/XDPoSChain/contracts/randomize/contract" "github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/txpool" "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/params" ) const ( extraVanity = 32 // Fixed number of extra-data prefix bytes reserved for signer vanity extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal ) type RewardLog struct { Sign uint64 `json:"sign"` Reward *big.Int `json:"reward"` } var TxSignMu sync.RWMutex // Send tx sign for block number to smart contract blockSigner. func CreateTransactionSign(chainConfig *params.ChainConfig, pool *txpool.TxPool, manager *accounts.Manager, block *types.Block, chainDb ethdb.Database, eb common.Address) error { TxSignMu.Lock() defer TxSignMu.Unlock() if chainConfig.XDPoS != nil { // Find active account. account := accounts.Account{} var wallet accounts.Wallet etherbaseAccount := accounts.Account{ Address: eb, URL: accounts.URL{}, } if wallets := manager.Wallets(); len(wallets) > 0 { if w, err := manager.Find(etherbaseAccount); err == nil && w != nil { wallet = w account = etherbaseAccount } else { wallet = wallets[0] if accts := wallets[0].Accounts(); len(accts) > 0 { account = accts[0] } } } // Create and send tx to smart contract for sign validate block. nonce := pool.Nonce(account.Address) tx := CreateTxSign(block.Number(), block.Hash(), nonce, common.BlockSignersBinary) txSigned, err := wallet.SignTx(account, tx, chainConfig.ChainID) if err != nil { log.Error("Fail to create tx sign", "error", err) return err } // Add tx signed to local tx pool. err = pool.Add([]*txpool.Transaction{{Tx: txSigned}}, true, true)[0] if err != nil { log.Error("Fail to add tx sign to local pool.", "error", err, "number", block.NumberU64(), "hash", block.Hash().Hex(), "from", account.Address, "nonce", nonce) return err } // Create secret tx. blockNumber := block.Number().Uint64() checkNumber := blockNumber % chainConfig.XDPoS.Epoch // Generate random private key and save into chaindb. exist := rawdb.HasRandomize(chainDb) // Set secret for randomize. if !exist && checkNumber > 0 && common.EpocBlockSecret <= checkNumber && common.EpocBlockOpening > checkNumber { // Only process when private key empty in state db. // Save randomize key into state db. randomizeKeyValue := RandStringByte(32) tx, err := BuildTxSecretRandomize(nonce+1, common.RandomizeSMCBinary, chainConfig.XDPoS.Epoch, randomizeKeyValue) if err != nil { log.Error("Fail to get tx opening for randomize", "error", err) return err } txSigned, err := wallet.SignTx(account, tx, chainConfig.ChainID) if err != nil { log.Error("Fail to create tx secret", "error", err) return err } // Add tx signed to local tx pool. err = pool.Add([]*txpool.Transaction{{Tx: txSigned}}, true, true)[0] if err != nil { log.Error("Fail to add tx secret to local pool.", "error", err, "number", block.NumberU64(), "hash", block.Hash().Hex(), "from", account.Address, "nonce", nonce) return err } // Put randomize key into chainDb. rawdb.WriteRandomize(chainDb, randomizeKeyValue) } // Set opening for randomize. if exist && checkNumber > 0 && common.EpocBlockOpening <= checkNumber && common.EpocBlockRandomize >= checkNumber { randomizeKeyValue, err := rawdb.ReadRandomize(chainDb) if err != nil { log.Error("Fail to get randomize key from state db.", "error", err) return err } tx, err := BuildTxOpeningRandomize(nonce+1, common.RandomizeSMCBinary, randomizeKeyValue) if err != nil { log.Error("Fail to get tx opening for randomize", "error", err) return err } txSigned, err := wallet.SignTx(account, tx, chainConfig.ChainID) if err != nil { log.Error("Fail to create tx opening", "error", err) return err } // Add tx to pool. err = pool.Add([]*txpool.Transaction{{Tx: txSigned}}, true, true)[0] if err != nil { log.Error("Fail to add tx opening to local pool.", "error", err, "number", block.NumberU64(), "hash", block.Hash().Hex(), "from", account.Address, "nonce", nonce) return err } // Clear randomize key in state db. rawdb.DeleteRandomize(chainDb) } } return nil } // Create tx sign. func CreateTxSign(blockNumber *big.Int, blockHash common.Hash, nonce uint64, blockSigner common.Address) *types.Transaction { data := common.Hex2Bytes(common.HexSignMethod) inputData := append(data, common.LeftPadBytes(blockNumber.Bytes(), 32)...) inputData = append(inputData, common.LeftPadBytes(blockHash.Bytes(), 32)...) tx := types.NewTransaction(nonce, blockSigner, big.NewInt(0), 200000, big.NewInt(0), inputData) return tx } // Send secret key into randomize smartcontract. func BuildTxSecretRandomize(nonce uint64, randomizeAddr common.Address, epocNumber uint64, randomizeKey []byte) (*types.Transaction, error) { data := common.Hex2Bytes(common.HexSetSecret) rand.Seed(time.Now().UnixNano()) secretNumb := rand.Intn(int(epocNumber)) // Append randomize suffix in -1, 0, 1. secrets := []int64{int64(secretNumb)} sizeOfArray := int64(32) // Build extra data for tx with first position is size of array byte and second position are length of array byte. arrSizeOfSecrets := common.LeftPadBytes(new(big.Int).SetInt64(sizeOfArray).Bytes(), 32) arrLengthOfSecrets := common.LeftPadBytes(new(big.Int).SetInt64(int64(len(secrets))).Bytes(), 32) inputData := append(data, arrSizeOfSecrets...) inputData = append(inputData, arrLengthOfSecrets...) for _, secret := range secrets { encryptSecret := Encrypt(randomizeKey, new(big.Int).SetInt64(secret).String()) inputData = append(inputData, common.LeftPadBytes([]byte(encryptSecret), int(sizeOfArray))...) } tx := types.NewTransaction(nonce, randomizeAddr, big.NewInt(0), 200000, big.NewInt(0), inputData) return tx, nil } // Send opening to randomize SMC. func BuildTxOpeningRandomize(nonce uint64, randomizeAddr common.Address, randomizeKey []byte) (*types.Transaction, error) { data := common.Hex2Bytes(common.HexSetOpening) inputData := append(data, randomizeKey...) tx := types.NewTransaction(nonce, randomizeAddr, big.NewInt(0), 200000, big.NewInt(0), inputData) return tx, nil } // Get signers signed for blockNumber from blockSigner contract. func GetSignersFromContract(statedb *state.StateDB, block *types.Block) ([]common.Address, error) { return statedb.GetSigners(block), nil } // Get signers signed for blockNumber from blockSigner contract. func GetSignersByExecutingEVM(addrBlockSigner common.Address, client bind.ContractBackend, blockHash common.Hash) ([]common.Address, error) { blockSigner, err := contract.NewBlockSigner(addrBlockSigner, client) if err != nil { log.Error("Fail get instance of blockSigner", "error", err) return nil, err } opts := new(bind.CallOpts) addrs, err := blockSigner.GetSigners(opts, blockHash) if err != nil { log.Error("Fail get block signers", "error", err) return nil, err } return addrs, nil } // Get random from randomize contract. func GetRandomizeFromContract(client bind.ContractBackend, addrMasternode common.Address) (int64, error) { randomize, err := randomizeContract.NewXDCRandomize(common.RandomizeSMCBinary, client) if err != nil { log.Error("Fail to get instance of randomize", "error", err) } opts := new(bind.CallOpts) secrets, err := randomize.GetSecret(opts, addrMasternode) if err != nil { log.Error("Fail get secrets from randomize", "error", err) } opening, err := randomize.GetOpening(opts, addrMasternode) if err != nil { log.Error("Fail get opening from randomize", "error", err) } return DecryptRandomizeFromSecretsAndOpening(secrets, opening) } // Generate m2 listing from randomize array. func GenM2FromRandomize(randomizes []int64, lenSigners int64) ([]int64, error) { blockValidator := NewSlice(int64(0), lenSigners, 1) randIndexs := make([]int64, lenSigners) total := int64(0) for _, j := range randomizes { total += j } rand.Seed(total) for i := len(blockValidator) - 1; i >= 0; i-- { blockLength := len(blockValidator) - 1 if blockLength <= 1 { blockLength = 1 } randomIndex := int64(rand.Intn(blockLength)) temp := blockValidator[randomIndex] blockValidator[randomIndex] = blockValidator[i] blockValidator[i] = temp blockValidator = append(blockValidator[:i], blockValidator[i+1:]...) randIndexs[i] = temp } return randIndexs, nil } // Get validators from m2 array integer. func BuildValidatorFromM2(listM2 []int64) []byte { validatorBytes := make([]byte, 0, len(listM2)*utils.M2ByteLength) for _, numberM2 := range listM2 { // Convert number to byte. m2Byte := common.LeftPadBytes(fmt.Appendf(nil, "%d", numberM2), utils.M2ByteLength) validatorBytes = append(validatorBytes, m2Byte...) } return validatorBytes } // Decode validator hex string. func DecodeValidatorsHexData(validatorsStr string) ([]int64, error) { validatorsByte, err := hexutil.Decode(validatorsStr) if err != nil { return nil, err } return utils.ExtractValidatorsFromBytes(validatorsByte) } // Decrypt randomize from secrets and opening. func DecryptRandomizeFromSecretsAndOpening(secrets [][32]byte, opening [32]byte) (int64, error) { var random int64 if len(secrets) > 0 { for _, secret := range secrets { trimSecret := bytes.TrimLeft(secret[:], "\x00") decryptSecret := Decrypt(opening[:], string(trimSecret)) if isInt(decryptSecret) { intNumber, err := strconv.Atoi(decryptSecret) if err != nil { log.Error("Can not convert string to integer", "error", err) return -1, err } random = int64(intNumber) } } } return random, nil } // Calculate reward for reward checkpoint. func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header *types.Header, rCheckpoint uint64, totalSigner *uint64) (map[common.Address]*RewardLog, error) { // Not reward for singer of genesis block and only calculate reward at checkpoint block. number := header.Number.Uint64() prevCheckpoint := number - (rCheckpoint * 2) startBlockNumber := prevCheckpoint + 1 endBlockNumber := startBlockNumber + rCheckpoint - 1 signers := make(map[common.Address]*RewardLog) mapBlkHash := map[uint64]common.Hash{} data := make(map[common.Hash][]common.Address) for i := prevCheckpoint + (rCheckpoint * 2) - 1; i >= startBlockNumber; i-- { header = chain.GetHeader(header.ParentHash, i) mapBlkHash[i] = header.Hash() signingTxs, ok := c.GetCachedSigningTxs(header.Hash()) if !ok { log.Debug("Failed get from cached", "hash", header.Hash(), "number", i) block := chain.GetBlock(header.Hash(), i) txs := block.Transactions() if !chain.Config().IsTIPSigning(header.Number) { receipts := rawdb.ReadRawReceipts(c.GetDb(), header.Hash(), i) signingTxs = c.CacheNoneTIPSigningTxs(header, txs, receipts) } else { signingTxs = c.CacheSigningTxs(header.Hash(), txs) } } for _, tx := range signingTxs { blkHash := common.BytesToHash(tx.Data()[len(tx.Data())-32:]) from := *tx.From() data[blkHash] = append(data[blkHash], from) } } header = chain.GetHeader(header.ParentHash, prevCheckpoint) masternodes := c.GetMasternodesFromCheckpointHeader(header) for i := startBlockNumber; i <= endBlockNumber; i++ { if i%common.MergeSignRange == 0 || !chain.Config().IsTIP2019(big.NewInt(int64(i))) { addrs := data[mapBlkHash[i]] // Filter duplicate address. if len(addrs) > 0 { addrSigners := make(map[common.Address]bool) for _, masternode := range masternodes { for _, addr := range addrs { if addr == masternode { if _, ok := addrSigners[addr]; !ok { addrSigners[addr] = true } break } } } for addr := range addrSigners { _, exist := signers[addr] if exist { signers[addr].Sign++ } else { signers[addr] = &RewardLog{1, new(big.Int)} } *totalSigner++ } } } } log.Info("Calculate reward at checkpoint", "startBlock", startBlockNumber, "endBlock", endBlockNumber) return signers, nil } // Calculate reward for signers. func CalculateRewardForSigner(chainReward *big.Int, signers map[common.Address]*RewardLog, totalSigner uint64) (map[common.Address]*big.Int, error) { resultSigners := make(map[common.Address]*big.Int) // Add reward for signers. if totalSigner > 0 { for signer, rLog := range signers { // Add reward for signer. calcReward := new(big.Int) calcReward.Div(chainReward, new(big.Int).SetUint64(totalSigner)) calcReward.Mul(calcReward, new(big.Int).SetUint64(rLog.Sign)) rLog.Reward = calcReward resultSigners[signer] = calcReward } } log.Info("Signers data", "totalSigner", totalSigner, "totalReward", chainReward) for addr, signer := range signers { log.Debug("Signer reward", "signer", addr, "sign", signer.Sign, "reward", signer.Reward) } return resultSigners, nil } // Get candidate owner by address. func GetCandidatesOwnerBySigner(statedb *state.StateDB, signerAddr common.Address) common.Address { return statedb.GetCandidateOwner(signerAddr) } func CalculateRewardForHolders(foundationWalletAddr common.Address, state *state.StateDB, signer common.Address, calcReward *big.Int, blockNumber uint64) (map[common.Address]*big.Int, error) { rewards, err := GetRewardBalancesRate(foundationWalletAddr, state, signer, calcReward, blockNumber) if err != nil { return nil, err } return rewards, nil } func GetRewardBalancesRate(foundationWalletAddr common.Address, statedb *state.StateDB, masterAddr common.Address, totalReward *big.Int, blockNumber uint64) (map[common.Address]*big.Int, error) { owner := GetCandidatesOwnerBySigner(statedb, masterAddr) balances := make(map[common.Address]*big.Int) rewardMaster := new(big.Int).Mul(totalReward, new(big.Int).SetInt64(common.RewardMasterPercent)) rewardMaster = new(big.Int).Div(rewardMaster, new(big.Int).SetInt64(100)) balances[owner] = rewardMaster // Get voters for masternode. voters := statedb.GetVoters(masterAddr) if len(voters) > 0 { totalVoterReward := new(big.Int).Mul(totalReward, new(big.Int).SetUint64(common.RewardVoterPercent)) totalVoterReward = new(big.Int).Div(totalVoterReward, new(big.Int).SetUint64(100)) totalCap := new(big.Int) // Get voters capacities. voterCaps := make(map[common.Address]*big.Int) for _, voteAddr := range voters { if _, ok := voterCaps[voteAddr]; ok && common.TIP2019Block.Uint64() <= blockNumber { continue } voterCap := statedb.GetVoterCap(masterAddr, voteAddr) totalCap.Add(totalCap, voterCap) voterCaps[voteAddr] = voterCap } if totalCap.Cmp(new(big.Int).SetInt64(0)) > 0 { for addr, voteCap := range voterCaps { // Only valid voter has cap > 0. if voteCap.Cmp(new(big.Int).SetInt64(0)) > 0 { rcap := new(big.Int).Mul(totalVoterReward, voteCap) rcap = new(big.Int).Div(rcap, totalCap) if balances[addr] != nil { balances[addr].Add(balances[addr], rcap) } else { balances[addr] = rcap } } } } } foundationReward := new(big.Int).Mul(totalReward, new(big.Int).SetInt64(common.RewardFoundationPercent)) foundationReward = new(big.Int).Div(foundationReward, new(big.Int).SetInt64(100)) if blockNumber >= common.TIPUpgradeReward.Uint64() && balances[foundationWalletAddr] != nil { balances[foundationWalletAddr].Add(balances[foundationWalletAddr], foundationReward) } else { balances[foundationWalletAddr] = foundationReward } jsonHolders, err := json.Marshal(balances) if err != nil { log.Error("Fail to parse json holders", "error", err) return nil, err } log.Trace("Holders reward", "holders", string(jsonHolders), "masternode", masterAddr) return balances, nil } // Dynamic generate array sequence of numbers. func NewSlice(start int64, end int64, step int64) []int64 { s := make([]int64, end-start) for i := range s { s[i] = start start += step } return s } // Shuffle array. func Shuffle(slice []int64) []int64 { newSlice := make([]int64, len(slice)) copy(newSlice, slice) for i := 0; i < len(slice)-1; i++ { rand.Seed(time.Now().UnixNano()) randIndex := rand.Intn(len(newSlice)) x := newSlice[i] newSlice[i] = newSlice[randIndex] newSlice[randIndex] = x } return newSlice } // encrypt string to base64 crypto using AES func Encrypt(key []byte, text string) string { // key := []byte(keyText) plaintext := []byte(text) block, err := aes.NewCipher(key) if err != nil { log.Error("Fail to encrypt", "err", err) return "" } // The IV needs to be unique, but not secure. Therefore it's common to // include it at the beginning of the ciphertext. ciphertext := make([]byte, aes.BlockSize+len(plaintext)) iv := ciphertext[:aes.BlockSize] if _, err := io.ReadFull(cryptoRand.Reader, iv); err != nil { log.Error("Fail to encrypt iv", "err", err) return "" } stream := cipher.NewCFBEncrypter(block, iv) stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext) // convert to base64 return base64.URLEncoding.EncodeToString(ciphertext) } // decrypt from base64 to decrypted string func Decrypt(key []byte, cryptoText string) string { ciphertext, _ := base64.URLEncoding.DecodeString(cryptoText) block, err := aes.NewCipher(key) if err != nil { log.Error("Fail to decrypt", "err", err) return "" } // The IV needs to be unique, but not secure. Therefore it's common to // include it at the beginning of the ciphertext. if len(ciphertext) < aes.BlockSize { log.Error("ciphertext too short") return "" } iv := ciphertext[:aes.BlockSize] ciphertext = ciphertext[aes.BlockSize:] stream := cipher.NewCFBDecrypter(block, iv) // XORKeyStream can work in-place if the two arguments are the same. stream.XORKeyStream(ciphertext, ciphertext) return string(ciphertext[:]) } // Generate random string. func RandStringByte(n int) []byte { letterBytes := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789" b := make([]byte, n) for i := range b { rand.Seed(time.Now().UnixNano()) b[i] = letterBytes[rand.Intn(len(letterBytes))] } return b } // Helper function check string is numeric. func isInt(strNumber string) bool { if _, err := strconv.Atoi(strNumber); err == nil { return true } else { return false } }