go-ethereum/contracts/utils.go
Daniel Liu 8d93eb6e3d
fix(contracts): use pool nonce for imported block sign tx (#2213)
Use pool pending nonce when creating the imported-block sign transaction.

Previously the code used state nonce, which could lag behind pool nonce when a pending tx with the same sender already existed. In that case, adding the new sign tx could hit replacement checks and fail with 'replacement transaction underpriced'.

This change switches nonce selection to PoolNonce to avoid nonce reuse in the local pool and prevent repeated sign insertion failures after block import.
2026-03-19 14:26:58 +08:00

583 lines
19 KiB
Go

// 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 <http://www.gnu.org/licenses/>.
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]
}
}
}
// Use the pool's pending nonce so imported-block signing does not reuse
// an already-pending nonce and trigger replacement-underpriced errors.
nonce := pool.PoolNonce(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.AddLocal(txSigned, true)
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.AddLocal(txSigned, true)
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.AddLocal(txSigned, true)
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
}
}