Skip to content

Commit

Permalink
Merge branch 'develop' into remove-duplicate-function
Browse files Browse the repository at this point in the history
  • Loading branch information
lukema95 authored Oct 12, 2023
2 parents cb5ed0a + 32b135a commit 5299dcb
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 52 deletions.
63 changes: 32 additions & 31 deletions zetaclient/bitcoin_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const (
minConfirmations = 0
maxHeightDiff = 10000
dustOffset = 2000
bytesPerKB = 1000
)

func (ob *BitcoinChainClient) SetCoreParams(params observertypes.CoreParams) {
Expand Down Expand Up @@ -292,9 +293,12 @@ func (ob *BitcoinChainClient) observeInTx() error {

for _, inTx := range inTxs {
ob.logger.WatchInTx.Debug().Msgf("Processing inTx: %s", inTx.TxHash)
amount := big.NewFloat(inTx.Value)
amount = amount.Mul(amount, big.NewFloat(1e8))
amountInt, _ := amount.Int(nil)
sats, err := getSatoshis(inTx.Value)
if err != nil {
ob.logger.WatchInTx.Error().Err(err).Msgf("getSatoshis error: %s", err)
continue
}
amountInt := big.NewInt(sats)
message := hex.EncodeToString(inTx.MemoBytes)
zetaHash, err := ob.zetaClient.PostSend(
inTx.FromAddress,
Expand Down Expand Up @@ -346,22 +350,31 @@ func (ob *BitcoinChainClient) IsSendOutTxProcessed(sendHash string, nonce uint64
res, included := ob.includedTxResults[outTxID]
ob.mu.Unlock()

// Get original cctx parameters
params, err := ob.GetPendingCctxParams(nonce)
if err != nil {
ob.logger.ObserveOutTx.Info().Msgf("IsSendOutTxProcessed: can't find pending cctx for nonce %d", nonce)
return false, false, err
}

if !included {
if !broadcasted {
return false, false, nil
}
// Get original cctx parameters
params, err := ob.GetPendingCctxParams(nonce)
if err != nil {
ob.logger.ObserveOutTx.Info().Msgf("IsSendOutTxProcessed: can't find pending cctx for nonce %d", nonce)
return false, false, nil
// If the broadcasted outTx is nonce 0, just wait for inclusion and don't schedule more keysign
// Schedule more than one keysign for nonce 0 can lead to duplicate payments.
// One purpose of nonce mark UTXO is to avoid duplicate payment based on the fact that Bitcoin
// prevents double spending of same UTXO. However, for nonce 0, we don't have a prior nonce (e.g., -1)
// for the signer to check against when making the payment. Signer treats nonce 0 as a special case in downstream code.
if nonce == 0 {
return true, false, nil
}

// Try including this outTx broadcasted by myself
inMempool, err := ob.checkNSaveIncludedTx(txnHash, params)
if err != nil {
ob.logger.ObserveOutTx.Error().Err(err).Msg("IsSendOutTxProcessed: checkNSaveIncludedTx failed")
return false, false, nil
return false, false, err
}
if inMempool { // to avoid unnecessary Tss keysign
ob.logger.ObserveOutTx.Info().Msgf("IsSendOutTxProcessed: outTx %s is still in mempool", outTxID)
Expand All @@ -378,27 +391,16 @@ func (ob *BitcoinChainClient) IsSendOutTxProcessed(sendHash string, nonce uint64
ob.logger.ObserveOutTx.Info().Msgf("IsSendOutTxProcessed: checkNSaveIncludedTx succeeded for outTx %s", outTxID)
}

var amount float64
if res.Amount > 0 {
ob.logger.ObserveOutTx.Warn().Msg("IsSendOutTxProcessed: res.Amount > 0")
amount = res.Amount
} else if res.Amount == 0 {
ob.logger.ObserveOutTx.Error().Msg("IsSendOutTxProcessed: res.Amount == 0")
return false, false, nil
} else {
amount = -res.Amount
}

amountInSat, _ := big.NewFloat(amount * 1e8).Int(nil)
// It's safe to use cctx's amount to post confirmation because it has already been verified in observeOutTx()
amountInSat := params.Amount.BigInt()
if res.Confirmations < ob.ConfirmationsThreshold(amountInSat) {
return true, false, nil
}

logger.Debug().Msgf("Bitcoin outTx confirmed: txid %s, amount %f\n", res.TxID, res.Amount)
logger.Debug().Msgf("Bitcoin outTx confirmed: txid %s, amount %s\n", res.TxID, amountInSat.String())
zetaHash, err := ob.zetaClient.PostReceiveConfirmation(
sendHash,
res.TxID,
// #nosec G701 always positive
uint64(res.BlockIndex),
0, // gas used not used with Bitcoin
nil, // gas price not used with Bitcoin
Expand Down Expand Up @@ -459,14 +461,14 @@ func (ob *BitcoinChainClient) PostGasPrice() error {
if feeResult.Errors != nil || feeResult.FeeRate == nil {
return fmt.Errorf("error getting gas price: %s", feeResult.Errors)
}
gasPrice := big.NewFloat(0)
gasPriceU64, _ := gasPrice.Mul(big.NewFloat(*feeResult.FeeRate), big.NewFloat(1e8)).Uint64()
feeRate := new(big.Int).SetInt64(int64(*feeResult.FeeRate * 1e8))
feeRatePerByte := new(big.Int).Div(feeRate, big.NewInt(bytesPerKB))
bn, err := ob.rpcClient.GetBlockCount()
if err != nil {
return err
}
// #nosec G701 always positive
zetaHash, err := ob.zetaClient.PostGasPrice(ob.chain, gasPriceU64, "100", uint64(bn))
zetaHash, err := ob.zetaClient.PostGasPrice(ob.chain, feeRatePerByte.Uint64(), "100", uint64(bn))
if err != nil {
ob.logger.WatchGasPrice.Err(err).Msg("PostGasPrice:")
return err
Expand Down Expand Up @@ -662,10 +664,9 @@ func (ob *BitcoinChainClient) refreshPendingNonce() {
pendingNonce := ob.pendingNonce
ob.mu.Unlock()

// #nosec G701 always positive
// #nosec G701 always non-negative
nonceLow := uint64(p.NonceLow)

if nonceLow > 0 && nonceLow >= pendingNonce {
if nonceLow > pendingNonce {
// get the last included outTx hash
txid, err := ob.getOutTxidByNonce(nonceLow-1, false)
if err != nil {
Expand Down Expand Up @@ -887,9 +888,9 @@ func (ob *BitcoinChainClient) checkNSaveIncludedTx(txHash string, params types.O
ob.includedTxHashes[txHash] = params.OutboundTxTssNonce
ob.includedTxResults[outTxID] = *getTxResult
if params.OutboundTxTssNonce >= ob.pendingNonce { // try increasing pending nonce on every newly included outTx
ob.pendingNonce = params.OutboundTxTssNonce
ob.pendingNonce = params.OutboundTxTssNonce + 1
}
ob.logger.ObserveOutTx.Info().Msgf("checkNSaveIncludedTx: included new bitcoin outTx %s outTxID %s", txHash, outTxID)
ob.logger.ObserveOutTx.Info().Msgf("checkNSaveIncludedTx: included new bitcoin outTx %s outTxID %s pending nonce %d", txHash, outTxID, ob.pendingNonce)
}
// update saved tx result as confirmations may increase
if foundHash && foundRes {
Expand Down
43 changes: 30 additions & 13 deletions zetaclient/btc_signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ import (

const (
maxNoOfInputsPerTx = 20
outTxBytesMin = 400 // 500B is a conservative estimate for a 2-input, 3-output SegWit tx
outTxBytesMax = 4_000 // 4KB is a conservative estimate for a 21-input, 3-output SegWit tx
outTxBytesCap = 10_000 // in case of accident

// for ZRC20 configuration
bytesPerInput = 150 // each input is about 150 bytes
ZRC20GasLimit = outTxBytesMin + bytesPerInput*8 // 1600B a suggested ZRC20 GAS_LIMIT for a 10-input, 3-output SegWit tx
)

type BTCSigner struct {
Expand Down Expand Up @@ -59,9 +66,9 @@ func NewBTCSigner(cfg config.BTCConfig, tssSigner TSSSigner, logger zerolog.Logg
}

// SignWithdrawTx receives utxos sorted by value, amount in BTC, feeRate in BTC per Kb
func (signer *BTCSigner) SignWithdrawTx(to *btcutil.AddressWitnessPubKeyHash, amount float64, gasPrice *big.Int, btcClient *BitcoinChainClient, height uint64, nonce uint64, chain *common.Chain) (*wire.MsgTx, error) {
estimateFee := 0.0001 // 10,000 sats, should be good for testnet
minFee := 0.00005
func (signer *BTCSigner) SignWithdrawTx(to *btcutil.AddressWitnessPubKeyHash, amount float64, gasPrice *big.Int, sizeLimit uint64,
btcClient *BitcoinChainClient, height uint64, nonce uint64, chain *common.Chain) (*wire.MsgTx, error) {
estimateFee := float64(gasPrice.Uint64()) * outTxBytesMax / 1e8
nonceMark := NonceMarkAmount(nonce)

// refresh unspent UTXOs and continue with keysign regardless of error
Expand Down Expand Up @@ -93,16 +100,25 @@ func (signer *BTCSigner) SignWithdrawTx(to *btcutil.AddressWitnessPubKeyHash, am
return nil, err
}

// fee checking
fees := new(big.Int).Mul(big.NewInt(int64(tx.SerializeSize())), gasPrice)
fees.Div(fees, big.NewInt(1000)) //FIXME: feeRate KB is 1000B or 1024B?
// #nosec G701 always in range
if fees.Int64() < int64(minFee*1e8) {
fmt.Printf("fees %d is less than minFee %f; use minFee", fees, minFee*1e8)
// #nosec G701 always in range
fees = big.NewInt(int64(minFee * 1e8))
// size checking
txSize := uint64(tx.SerializeSize())
if txSize > sizeLimit { // ZRC20 'withdraw' charged less fee from end user
signer.logger.Info().Msgf("sizeLimit %d is less than txSize %d for nonce %d", sizeLimit, txSize, nonce)
}
if txSize < outTxBytesMin { // outbound shouldn't be blocked a low sizeLimit
signer.logger.Warn().Msgf("sizeLimit %d is less than outTxBytesMin %d; use outTxBytesMin", sizeLimit, outTxBytesMin)
txSize = outTxBytesMin
}
if txSize > outTxBytesCap { // in case of accident
signer.logger.Warn().Msgf("sizeLimit %d is greater than outTxBytesCap %d; use outTxBytesCap", sizeLimit, outTxBytesCap)
txSize = outTxBytesCap
}

// fee calculation
fees := new(big.Int).Mul(big.NewInt(int64(txSize)), gasPrice)
fees.Div(fees, big.NewInt(bytesPerKB))
signer.logger.Info().Msgf("bitcoin outTx nonce %d gasPrice %s size %d fees %s", nonce, gasPrice.String(), txSize, fees.String())

// calculate remaining btc to TSS self
tssAddrWPKH := signer.tssSigner.BTCAddressWitnessPubkeyHash()
payToSelf, err := payToWitnessPubKeyHashScript(tssAddrWPKH.WitnessProgram())
Expand Down Expand Up @@ -253,8 +269,9 @@ func (signer *BTCSigner) TryProcessOutTx(send *types.CrossChainTx, outTxMan *Out
return
}

sizelimit := params.OutboundTxGasLimit
gasprice, ok := new(big.Int).SetString(params.OutboundTxGasPrice, 10)
if !ok {
if !ok || gasprice.Cmp(big.NewInt(0)) < 0 {
logger.Error().Msgf("cannot convert gas price %s ", params.OutboundTxGasPrice)
return
}
Expand All @@ -273,7 +290,7 @@ func (signer *BTCSigner) TryProcessOutTx(send *types.CrossChainTx, outTxMan *Out

logger.Info().Msgf("SignWithdrawTx: to %s, value %d sats", addr.EncodeAddress(), params.Amount.Uint64())
logger.Info().Msgf("using utxos: %v", btcClient.utxos)
tx, err := signer.SignWithdrawTx(to, float64(params.Amount.Uint64())/1e8, gasprice, btcClient, height,
tx, err := signer.SignWithdrawTx(to, float64(params.Amount.Uint64())/1e8, gasprice, sizelimit, btcClient, height,
outboundTxTssNonce, &btcClient.chain)
if err != nil {
logger.Warn().Err(err).Msgf("SignOutboundTx error: nonce %d chain %d", outboundTxTssNonce, params.ReceiverChainId)
Expand Down
14 changes: 7 additions & 7 deletions zetaclient/evm_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ func (ob *EVMChainClient) IsSendOutTxProcessed(sendHash string, nonce uint64, co
if err != nil {
logger.Error().Err(err).Msg("error posting confirmation to meta core")
}
logger.Info().Msgf("Zeta tx hash: %s\n", zetaHash)
logger.Info().Msgf("Zeta tx hash: %s cctx %s nonce %d", zetaHash, sendHash, nonce)
return true, true, nil

} else if cointype == common.CoinType_Gas { // the outbound is a regular Ether/BNB/Matic transfer; no need to check events
Expand All @@ -278,7 +278,7 @@ func (ob *EVMChainClient) IsSendOutTxProcessed(sendHash string, nonce uint64, co
if err != nil {
logger.Error().Err(err).Msg("error posting confirmation to meta core")
}
logger.Info().Msgf("Zeta tx hash: %s\n", zetaHash)
logger.Info().Msgf("Zeta tx hash: %s cctx %s nonce %d", zetaHash, sendHash, nonce)
return true, true, nil
} else if receipt.Status == 0 { // the same as below events flow
logger.Info().Msgf("Found (failed tx) sendHash %s on chain %s txhash %s", sendHash, ob.chain.String(), receipt.TxHash.Hex())
Expand All @@ -298,7 +298,7 @@ func (ob *EVMChainClient) IsSendOutTxProcessed(sendHash string, nonce uint64, co
if err != nil {
logger.Error().Err(err).Msgf("PostReceiveConfirmation error in WatchTxHashWithTimeout; zeta tx hash %s", zetaTxHash)
}
logger.Info().Msgf("Zeta tx hash: %s", zetaTxHash)
logger.Info().Msgf("Zeta tx hash: %s cctx %s nonce %d", zetaTxHash, sendHash, nonce)
return true, true, nil
}
} else if cointype == common.CoinType_Zeta { // the outbound is a Zeta transfer; need to check events ZetaReceived
Expand Down Expand Up @@ -344,7 +344,7 @@ func (ob *EVMChainClient) IsSendOutTxProcessed(sendHash string, nonce uint64, co
logger.Error().Err(err).Msg("error posting confirmation to meta core")
continue
}
logger.Info().Msgf("Zeta tx hash: %s\n", zetaHash)
logger.Info().Msgf("Zeta tx hash: %s cctx %s nonce %d", zetaHash, sendHash, nonce)
return true, true, nil
}
// #nosec G701 always in range
Expand Down Expand Up @@ -380,7 +380,7 @@ func (ob *EVMChainClient) IsSendOutTxProcessed(sendHash string, nonce uint64, co
logger.Err(err).Msg("error posting confirmation to meta core")
continue
}
logger.Info().Msgf("Zeta tx hash: %s", metaHash)
logger.Info().Msgf("Zeta tx hash: %s cctx %s nonce %d", metaHash, sendHash, nonce)
return true, true, nil
}
// #nosec G701 always in range
Expand All @@ -407,7 +407,7 @@ func (ob *EVMChainClient) IsSendOutTxProcessed(sendHash string, nonce uint64, co
if err != nil {
logger.Error().Err(err).Msgf("PostReceiveConfirmation error in WatchTxHashWithTimeout; zeta tx hash %s", zetaTxHash)
}
logger.Info().Msgf("Zeta tx hash: %s", zetaTxHash)
logger.Info().Msgf("Zeta tx hash: %s cctx %s nonce %d", zetaTxHash, sendHash, nonce)
return true, true, nil
}
} else if cointype == common.CoinType_ERC20 {
Expand Down Expand Up @@ -446,7 +446,7 @@ func (ob *EVMChainClient) IsSendOutTxProcessed(sendHash string, nonce uint64, co
logger.Error().Err(err).Msg("error posting confirmation to meta core")
continue
}
logger.Info().Msgf("Zeta tx hash: %s\n", zetaHash)
logger.Info().Msgf("Zeta tx hash: %s cctx %s nonce %d", zetaHash, sendHash, nonce)
return true, true, nil
}
// #nosec G701 always in range
Expand Down
7 changes: 6 additions & 1 deletion zetaclient/zetacore_observer.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,12 @@ func (co *CoreObserver) startSendScheduler() {
outTxID := fmt.Sprintf("%s-%d-%d", cctx.Index, params.ReceiverChainId, nonce) // would outTxID a better ID?

// Process Bitcoin OutTx
if common.IsBitcoinChain(c.ChainId) && !outTxMan.IsOutTxActive(outTxID) {
if common.IsBitcoinChain(c.ChainId) {
if outTxMan.IsOutTxActive(outTxID) {
// bitcoun outTx is processed sequencially by nonce
// if the current outTx is being processed, there is no need to process outTx with future nonces
break
}
// #nosec G701 positive
if stop := co.processBitcoinOutTx(outTxMan, uint64(idx), cctx, signer, ob, currentHeight); stop {
break
Expand Down

0 comments on commit 5299dcb

Please sign in to comment.