Skip to content

Commit

Permalink
Added finding target wallets
Browse files Browse the repository at this point in the history
  • Loading branch information
tomaszslabon committed Jan 5, 2024
1 parent f8bdbef commit 875f5f5
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 77 deletions.
1 change: 1 addition & 0 deletions pkg/chain/ethereum/tbtc.go
Original file line number Diff line number Diff line change
Expand Up @@ -1705,6 +1705,7 @@ func (tc *TbtcChain) ValidateHeartbeatProposal(
}

func (tc *TbtcChain) ValidateMovingFundsProposal(
walletPublicKeyHash [20]byte,
proposal *tbtc.MovingFundsProposal,
) error {
// TODO: Implement
Expand Down
29 changes: 4 additions & 25 deletions pkg/tbtc/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,22 +188,6 @@ type BridgeChain interface {
// if the wallet was not found.
GetWallet(walletPublicKeyHash [20]byte) (*WalletChainData, error)

// GetWalletParameters gets the current value of parameters relevant to
// wallet.
GetWalletParameters() (
creationPeriod uint32,
creationMinBtcBalance uint64,
creationMaxBtcBalance uint64,
closureMinBtcBalance uint64,
maxAge uint32,
maxBtcTransfer uint64,
closingPeriod uint32,
err error,
)

// GetLiveWalletsCount gets the current count of live wallets.
GetLiveWalletsCount() (uint32, error)

// ComputeMainUtxoHash computes the hash of the provided main UTXO
// according to the on-chain Bridge rules.
ComputeMainUtxoHash(mainUtxo *bitcoin.UnspentTransactionOutput) [32]byte
Expand Down Expand Up @@ -238,14 +222,6 @@ type BridgeChain interface {
fundingOutputIndex uint32,
) (*DepositChainRequest, bool, error)

// PastNewWalletRegisteredEvents fetches past new wallet registered events
// according to the provided filter or unfiltered if the filter is nil. Returned
// events are sorted by the block number in the ascending order, i.e. the
// latest event is at the end of the slice.
PastNewWalletRegisteredEvents(
filter *NewWalletRegisteredEventFilter,
) ([]*NewWalletRegisteredEvent, error)

// Submits the moving funds target wallets commitment.
SubmitMovingFundsCommitment(
walletPublicKeyHash [20]byte,
Expand Down Expand Up @@ -386,7 +362,10 @@ type WalletProposalValidatorChain interface {
// ValidateMovingFundsProposal validates the given moving funds proposal
// against the chain. Returns an error if the proposal is not valid or
// nil otherwise.
ValidateMovingFundsProposal(proposal *MovingFundsProposal) error
ValidateMovingFundsProposal(
walletPublicKeyHash [20]byte,
proposal *MovingFundsProposal,
) error
}

// RedemptionRequestedEvent represents a redemption requested event.
Expand Down
15 changes: 8 additions & 7 deletions pkg/tbtc/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -821,13 +821,6 @@ func (lc *localChain) ValidateRedemptionProposal(
return nil
}

func (lc *localChain) ValidateMovingFundsProposal(
proposal *MovingFundsProposal,
) error {
// TODO: Implement
return nil
}

func (lc *localChain) setRedemptionProposalValidationResult(
walletPublicKeyHash [20]byte,
proposal *RedemptionProposal,
Expand Down Expand Up @@ -895,6 +888,14 @@ func (lc *localChain) setHeartbeatProposalValidationResult(
lc.heartbeatProposalValidations[proposal.Message] = result
}

func (lc *localChain) ValidateMovingFundsProposal(
walletPublicKeyHash [20]byte,
proposal *MovingFundsProposal,
) error {
// TODO: Implement
panic("unsupported")
}

// Connect sets up the local chain.
func Connect(blockTime ...time.Duration) *localChain {
operatorPrivateKey, _, err := operator.GenerateKeyPair(local_v1.DefaultCurve)
Expand Down
24 changes: 24 additions & 0 deletions pkg/tbtcpg/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@ type Chain interface {
filter *tbtc.NewWalletRegisteredEventFilter,
) ([]*tbtc.NewWalletRegisteredEvent, error)

// GetWalletParameters gets the current value of parameters relevant to
// wallet.
GetWalletParameters() (
creationPeriod uint32,
creationMinBtcBalance uint64,
creationMaxBtcBalance uint64,
closureMinBtcBalance uint64,
maxAge uint32,
maxBtcTransfer uint64,
closingPeriod uint32,
err error,
)

// GetLiveWalletsCount gets the current count of live wallets.
GetLiveWalletsCount() (uint32, error)

// BuildDepositKey calculates a deposit key for the given funding transaction
// which is a unique identifier for a deposit on-chain.
BuildDepositKey(fundingTxHash bitcoin.Hash, fundingOutputIndex uint32) *big.Int
Expand Down Expand Up @@ -111,4 +127,12 @@ type Chain interface {
walletPublicKeyHash [20]byte,
proposal *tbtc.HeartbeatProposal,
) error

// ValidateMovingFundsProposal validates the given moving funds proposal
// against the chain. Returns an error if the proposal is not valid or
// nil otherwise.
ValidateMovingFundsProposal(
walletPublicKeyHash [20]byte,
proposal *tbtc.MovingFundsProposal,
) error
}
16 changes: 16 additions & 0 deletions pkg/tbtcpg/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,22 @@ func (lc *LocalChain) SetHeartbeatProposalValidationResult(
lc.heartbeatProposalValidations[proposal.Message] = result
}

func (lc *LocalChain) ValidateMovingFundsProposal(
walletPublicKeyHash [20]byte,
proposal *tbtc.MovingFundsProposal,
) error {
// TODO: Implement
panic("unsupported")
}

func (lc *LocalChain) SetMovingFundsProposalValidationResult(
proposal *tbtc.MovingFundsProposal,
result bool,
) {
// TODO: Implement
panic("unsupported")
}

func buildRedemptionProposalValidationKey(
walletPublicKeyHash [20]byte,
proposal *tbtc.RedemptionProposal,
Expand Down
186 changes: 141 additions & 45 deletions pkg/tbtcpg/moving_funds.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package tbtcpg

import (
"fmt"
"math/big"
"sort"

"go.uber.org/zap"

Expand Down Expand Up @@ -38,14 +40,30 @@ func (mft *MovingFundsTask) Run(request *tbtc.CoordinationProposalRequest) (
zap.String("walletPKH", fmt.Sprintf("0x%x", walletPublicKeyHash)),
)

liveWalletsCount, err := mft.chain.GetLiveWalletsCount()
// Check if the wallet is eligible for moving funds.
walletChainData, err := mft.chain.GetWallet(walletPublicKeyHash)
if err != nil {
return nil, false, fmt.Errorf(
"cannot get Live wallets count: [%w]",
"cannot get source wallet's chain data: [%w]",
err,
)
}

if walletChainData.State != tbtc.StateMovingFunds {
taskLogger.Infof("source wallet not in MoveFunds state")
return nil, false, nil
}

if walletChainData.PendingRedemptionsValue > 0 {
taskLogger.Infof("source wallet has pending redemptions")
return nil, false, nil
}

if walletChainData.PendingMovedFundsSweepRequestsCount > 0 {
taskLogger.Infof("source wallet has pending moved funds sweep requests")
return nil, false, nil
}

walletMainUtxo, err := tbtc.DetermineWalletMainUtxo(
walletPublicKeyHash,
mft.chain,
Expand All @@ -63,35 +81,32 @@ func (mft *MovingFundsTask) Run(request *tbtc.CoordinationProposalRequest) (
walletBalance = walletMainUtxo.Value
}

walletChainData, err := mft.chain.GetWallet(walletPublicKeyHash)
if err != nil {
return nil, false, fmt.Errorf(
"cannot get source wallet's chain data: [%w]",
err,
)
if walletBalance <= 0 {
// The wallet's balance cannot be `0`. Since we are dealing with
// a signed integer we also check it's not negative just in case.
taskLogger.Infof("source wallet does not have a positive balance")
return nil, false, nil
}

ok, err := mft.CheckEligibility(
taskLogger,
liveWalletsCount,
walletBalance,
walletChainData,
)
liveWalletsCount, err := mft.chain.GetLiveWalletsCount()
if err != nil {
return nil, false, fmt.Errorf(
"cannot check wallet eligibility: [%w]",
"cannot get Live wallets count: [%w]",
err,
)
}

if !ok {
taskLogger.Info("wallet not eligible for moving funds")
if liveWalletsCount == 0 {
taskLogger.Infof("there are no Live wallets available")
return nil, false, nil
}

targetWallets, commitmentSubmitted, err := mft.FindTargetWallets(
taskLogger,
walletPublicKeyHash,
walletChainData,
uint64(walletBalance),
liveWalletsCount,
)
if err != nil {
return nil, false, fmt.Errorf("cannot find target wallets: [%w]", err)
Expand All @@ -117,52 +132,133 @@ func (mft *MovingFundsTask) Run(request *tbtc.CoordinationProposalRequest) (

func (mft *MovingFundsTask) FindTargetWallets(
taskLogger log.StandardLogger,
sourceWalletPublicKeyHash [20]byte,
walletChainData *tbtc.WalletChainData,
walletBalance uint64,
liveWalletsCount uint32,
) ([][20]byte, bool, error) {
if walletChainData.MovingFundsTargetWalletsCommitmentHash == [32]byte{} {
taskLogger.Infof("Move funds commitment has not been submitted yet")
// TODO: Find the target wallets among Live wallets.
} else {
taskLogger.Infof("Move funds commitment has already been submitted")
// TODO: Find the target wallets from the emitted event.
return mft.findNewTargetWallets(
taskLogger,
sourceWalletPublicKeyHash,
walletBalance,
liveWalletsCount,
)
}
return nil, false, nil

return mft.retrieveCommittedTargetWallets(taskLogger)
}

func (mft *MovingFundsTask) CheckEligibility(
func (mft *MovingFundsTask) findNewTargetWallets(
taskLogger log.StandardLogger,
sourceWalletPublicKeyHash [20]byte,
walletBalance uint64,
liveWalletsCount uint32,
walletBalance int64,
walletChainData *tbtc.WalletChainData,
) (bool, error) {
if liveWalletsCount == 0 {
taskLogger.Infof("there are no Live wallets available")
return false, nil
) ([][20]byte, bool, error) {
taskLogger.Infof(
"commitment not submitted yet; looking for new target wallets",
)

_, _, _, _, _, walletMaxBtcTransfer, _, err := mft.chain.GetWalletParameters()
if err != nil {
return nil, false, fmt.Errorf("cannot get wallet parameters: [%w]", err)
}

if walletBalance <= 0 {
// The wallet's balance cannot be `0`. Since we are dealing with
// a signed integer we also check it's not negative just in case.
taskLogger.Infof("source wallet does not have a positive balance")
return false, nil
if walletMaxBtcTransfer == 0 {
return nil, false, fmt.Errorf(
"wallet max BTC transfer must be positive: [%w]", err,
)
}

if walletChainData.State != tbtc.StateMovingFunds {
taskLogger.Infof("source wallet not in MoveFunds state")
return false, nil
ceilingDivide := func(x, y uint64) uint64 {
// The divisor must be positive, but we do not need to check it as
// this function will be executed with wallet max BTC transfer as
// the divisor and we already ensured it is positive.
return (x + y - 1) / y
}
min := func(x, y uint64) uint64 {
if x < y {
return x
}
return y
}

if walletChainData.PendingRedemptionsValue > 0 {
taskLogger.Infof("source wallet has pending redemptions")
return false, nil
targetWalletsCount := min(
uint64(liveWalletsCount),
ceilingDivide(walletBalance, walletMaxBtcTransfer),
)

// Prepare a list of target wallets using the new wallets registration
// events. Retrieve only the necessary number of live wallets.
// The iteration is started from the end of the list as the newest wallets
// are located there and have highest the chance of being Live.
events, err := mft.chain.PastNewWalletRegisteredEvents(nil)
if err != nil {
return nil, false, fmt.Errorf(
"failed to get past new wallet registered events: [%v]",
err,
)
}

if walletChainData.PendingMovedFundsSweepRequestsCount > 0 {
taskLogger.Infof("source wallet has pending moved funds sweep requests")
return false, nil
targetWallets := make([][20]byte, 0)

for i := len(events) - 1; i >= 0; i-- {
walletPubKeyHash := events[i].WalletPublicKeyHash
if walletPubKeyHash == sourceWalletPublicKeyHash {
// Just in case make sure not to include the source wallet
// itself.
continue
}

wallet, err := mft.chain.GetWallet(walletPubKeyHash)
if err != nil {
taskLogger.Errorf(
"failed to get wallet data for wallet with PKH [0x%x]: [%v]",
walletPubKeyHash,
err,
)
continue
}

if wallet.State == tbtc.StateLive {
targetWallets = append(targetWallets, walletPubKeyHash)
}
if len(targetWallets) == int(targetWalletsCount) {
// Stop the iteration if enough live wallets have been gathered.
break
}
}

return true, nil
if len(targetWallets) != int(targetWalletsCount) {
return nil, false, fmt.Errorf(
"failed to get enough target wallets: required [%v]; gathered [%v]",
targetWalletsCount,
len(targetWallets),
)
}

// Sort the target wallets according to their numerical representation
// as the on-chain contract expects.
sort.Slice(targetWallets, func(i, j int) bool {
bigIntI := new(big.Int).SetBytes(targetWallets[i][:])
bigIntJ := new(big.Int).SetBytes(targetWallets[j][:])
return bigIntI.Cmp(bigIntJ) < 0
})

logger.Infof("gathered [%v] target wallets", len(targetWallets))

return targetWallets, false, nil
}

func (mft *MovingFundsTask) retrieveCommittedTargetWallets(
taskLogger log.StandardLogger,
) ([][20]byte, bool, error) {
taskLogger.Infof(
"commitment already submitted; retrieving committed target wallets",
)

// TODO: Implement
return nil, false, nil
}

func (mft *MovingFundsTask) SubmitMovingFundsCommitment() ([][20]byte, error) {
Expand Down

0 comments on commit 875f5f5

Please sign in to comment.