go-ethereum/contracts/utils.go

578 lines
No EOL
19 KiB
Go

package contracts
import (
"bytes"
"crypto/aes"
"crypto/cipher"
cryptoRand "crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/contracts/blocksigner/contract"
randomizeContract "github.com/ethereum/go-ethereum/contracts/randomize/contract"
contractValidator "github.com/ethereum/go-ethereum/contracts/validator/contract"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/pkg/errors"
"io"
"math/big"
"math/rand"
"strconv"
"time"
)
const (
M2ByteLength = 4
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"`
}
// Send tx sign for block number to smart contract blockSigner.
func CreateTransactionSign(chainConfig *params.ChainConfig, pool *core.TxPool, manager *accounts.Manager, block *types.Block, chainDb ethdb.Database) error {
if chainConfig.XDPoS != nil {
// Find active account.
account := accounts.Account{}
var wallet accounts.Wallet
if wallets := manager.Wallets(); len(wallets) > 0 {
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.State().GetNonce(account.Address)
tx := CreateTxSign(block.Number(), block.Hash(), nonce, common.HexToAddress(common.BlockSigners))
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.AddLocal(txSigned)
if err != nil {
log.Error("Fail to add tx sign to local pool.", "error", err)
}
// Create secret tx.
blockNumber := block.Number().Uint64()
checkNumber := blockNumber % chainConfig.XDPoS.Epoch
// Generate random private key and save into chaindb.
randomizeKeyName := []byte("randomizeKey")
exist, _ := chainDb.Has(randomizeKeyName)
// 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.HexToAddress(common.RandomizeSMC), 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.AddLocal(txSigned)
if err != nil {
log.Error("Fail to add tx secret to local pool.", "error", err)
}
// Put randomize key into chainDb.
chainDb.Put(randomizeKeyName, randomizeKeyValue)
}
// Set opening for randomize.
if exist && checkNumber > 0 && common.EpocBlockOpening <= checkNumber && common.EpocBlockRandomize >= checkNumber {
randomizeKeyValue, err := chainDb.Get(randomizeKeyName)
if err != nil {
log.Error("Fail to get randomize key from state db.", "error", err)
}
tx, err := BuildTxOpeningRandomize(nonce+1, common.HexToAddress(common.RandomizeSMC), 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.AddLocal(txSigned)
if err != nil {
log.Error("Fail to add tx opening to local pool.", "error", err)
}
// Clear randomize key in state db.
chainDb.Delete(randomizeKeyName)
}
}
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), 4200000, 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), 4200000, big.NewInt(0), inputData)
return tx, nil
}
// Get signers signed for blockNumber from blockSigner contract.
func GetSignersFromContract(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.HexToAddress(common.RandomizeSMC), 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)
var temp 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 {
var validatorBytes []byte
for _, numberM2 := range listM2 {
// Convert number to byte.
m2Byte := common.LeftPadBytes([]byte(fmt.Sprintf("%d", numberM2)), M2ByteLength)
validatorBytes = append(validatorBytes, m2Byte...)
}
return validatorBytes
}
// Extract validators from byte array.
func ExtractValidatorsFromBytes(byteValidators []byte) []int64 {
lenValidator := len(byteValidators) / M2ByteLength
var validators []int64
for i := 0; i < lenValidator; i++ {
trimByte := bytes.Trim(byteValidators[i*M2ByteLength:(i+1)*M2ByteLength], "\x00")
intNumber, err := strconv.Atoi(string(trimByte))
if err != nil {
log.Error("Can not convert string to integer", "error", err)
}
validators = append(validators, int64(intNumber))
}
return validators
}
// Decode validator hex string.
func DecodeValidatorsHexData(validatorsStr string) ([]int64, error) {
validatorsByte, err := hexutil.Decode(validatorsStr)
if err != nil {
return nil, err
}
return ExtractValidatorsFromBytes(validatorsByte), nil
}
// 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)
}
random = int64(intNumber)
}
}
}
return random, nil
}
// Calculate reward for reward checkpoint.
func GetRewardForCheckpoint(chain consensus.ChainReader, blockSignerAddr common.Address, number uint64, rCheckpoint uint64, client bind.ContractBackend, totalSigner *uint64) (map[common.Address]*rewardLog, error) {
// Not reward for singer of genesis block and only calculate reward at checkpoint block.
startBlockNumber := number - (rCheckpoint * 2) + 1
endBlockNumber := startBlockNumber + rCheckpoint - 1
signers := make(map[common.Address]*rewardLog)
for i := startBlockNumber; i <= endBlockNumber; i++ {
block := chain.GetHeaderByNumber(i)
addrs, err := GetSignersFromContract(blockSignerAddr, client, block.Hash())
if err != nil {
log.Error("Fail to get signers from smartcontract.", "error", err, "blockNumber", i)
return nil, err
}
// Filter duplicate address.
if len(addrs) > 0 {
addrSigners := make(map[common.Address]bool)
for _, addr := range addrs {
if _, ok := addrSigners[addr]; !ok {
addrSigners[addr] = true
}
}
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
}
}
jsonSigners, err := json.Marshal(signers)
if err != nil {
log.Error("Fail to parse json signers", "error", err)
return nil, err
}
log.Info("Signers data", "signers", string(jsonSigners), "totalSigner", totalSigner, "totalReward", chainReward)
return resultSigners, nil
}
// Get candidate owner by address.
func GetCandidatesOwnerBySigner(validator *contractValidator.XDCValidator, signerAddr common.Address) common.Address {
owner := signerAddr
opts := new(bind.CallOpts)
owner, err := validator.GetCandidateOwner(opts, signerAddr)
if err != nil {
log.Error("Fail get candidate owner", "error", err)
return owner
}
return owner
}
// Calculate reward for holders.
func CalculateRewardForHolders(foudationWalletAddr common.Address, validator *contractValidator.XDCValidator, state *state.StateDB, signer common.Address, calcReward *big.Int) error {
rewards, err := GetRewardBalancesRate(foudationWalletAddr, signer, calcReward, validator)
if err != nil {
return err
}
if len(rewards) > 0 {
for holder, reward := range rewards {
state.AddBalance(holder, reward)
}
}
return nil
}
// Get reward balance rates for master node, founder and holders.
func GetRewardBalancesRate(foudationWalletAddr common.Address, masterAddr common.Address, totalReward *big.Int, validator *contractValidator.XDCValidator) (map[common.Address]*big.Int, error) {
owner := GetCandidatesOwnerBySigner(validator, 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.
opts := new(bind.CallOpts)
voters, err := validator.GetVoters(opts, masterAddr)
if err != nil {
log.Error("Fail to get voters", "error", err)
return nil, err
}
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 {
voterCap, err := validator.GetVoterCap(opts, masterAddr, voteAddr)
if err != nil {
log.Error("Fail to get vote capacity", "error", err)
return nil, err
}
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
}
}
}
}
}
foudationReward := new(big.Int).Mul(totalReward, new(big.Int).SetInt64(common.RewardFoundationPercent))
foudationReward = new(big.Int).Div(foudationReward, new(big.Int).SetInt64(100))
balances[foudationWalletAddr] = foudationReward
jsonHolders, err := json.Marshal(balances)
if err != nil {
log.Error("Fail to parse json holders", "error", err)
return nil, err
}
log.Info("Holders reward", "holders", string(jsonHolders), "master node", masterAddr.String())
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 {
panic(err)
}
// 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 {
panic(err)
}
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 {
panic(err)
}
// 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 {
panic("ciphertext too short")
}
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 fmt.Sprintf("%s", 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
}
}
// Get masternodes address from checkpoint Header.
func GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address {
masternodes := make([]common.Address, (len(checkpointHeader.Extra)-extraVanity-extraSeal)/common.AddressLength)
for i := 0; i < len(masternodes); i++ {
copy(masternodes[i][:], checkpointHeader.Extra[extraVanity+i*common.AddressLength:])
}
return masternodes
}
// Get m2 list from checkpoint block.
func GetM2FromCheckpointBlock(checkpointBlock types.Block) ([]common.Address, error) {
if checkpointBlock.Number().Int64()%common.EpocBlockRandomize != 0 {
return nil, errors.New("This block is not checkpoint block epoc.")
}
// Get singers from this block.
masternodes := GetMasternodesFromCheckpointHeader(checkpointBlock.Header())
validators := ExtractValidatorsFromBytes(checkpointBlock.Header().Validators)
var m2List []common.Address
lenMasternodes := len(masternodes)
var valAddr common.Address
for validatorIndex := range validators {
if validatorIndex < lenMasternodes {
valAddr = masternodes[validatorIndex]
} else {
valAddr = masternodes[validatorIndex-lenMasternodes]
}
m2List = append(m2List, valAddr)
}
return m2List, nil
}