Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional check when ensuring wallet synchronised between chains #3794

Merged
merged 4 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions pkg/chain/ethereum/tbtc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2026,7 +2026,7 @@ func (tc *TbtcChain) GetMovingFundsParameters() (
func (tc *TbtcChain) GetMovedFundsSweepRequest(
movingFundsTxHash bitcoin.Hash,
movingFundsTxOutpointIndex uint32,
) (*tbtc.MovedFundsSweepRequest, error) {
) (*tbtc.MovedFundsSweepRequest, bool, error) {
movedFundsKey := buildMovedFundsKey(
movingFundsTxHash,
movingFundsTxOutpointIndex,
Expand All @@ -2036,16 +2036,21 @@ func (tc *TbtcChain) GetMovedFundsSweepRequest(
movedFundsKey,
)
if err != nil {
return nil, fmt.Errorf(
return nil, false, fmt.Errorf(
"cannot get moved funds sweep request for key [0x%x]: [%v]",
movedFundsKey.Text(16),
err,
)
}

// Moved funds sweep request not found.
if movedFundsSweepRequest.CreatedAt == 0 {
return nil, false, nil
}

state, err := parseMovedFundsSweepRequestState(movedFundsSweepRequest.State)
if err != nil {
return nil, fmt.Errorf(
return nil, false, fmt.Errorf(
"cannot parse state for moved funds sweep request [0x%x]: [%v]",
movedFundsKey.Text(16),
err,
Expand All @@ -2057,7 +2062,7 @@ func (tc *TbtcChain) GetMovedFundsSweepRequest(
Value: movedFundsSweepRequest.Value,
CreatedAt: time.Unix(int64(movedFundsSweepRequest.CreatedAt), 0),
State: state,
}, nil
}, true, nil
}

func parseMovedFundsSweepRequestState(value uint8) (
Expand Down
13 changes: 4 additions & 9 deletions pkg/maintainer/spv/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,8 @@ type Chain interface {
) error

// GetDepositRequest gets the on-chain deposit request for the given
// funding transaction hash and output index.The returned values represent:
// - deposit request which is non-nil only when the deposit request was
// found,
// - boolean value which is true if the deposit request was found, false
// otherwise,
// - error which is non-nil only when the function execution failed. It will
// be nil if the deposit request was not found, but the function execution
// succeeded.
// funding transaction hash and output index. The returned bool value
// indicates whether the request was found or not.
GetDepositRequest(
fundingTxHash bitcoin.Hash,
fundingOutputIndex uint32,
Expand Down Expand Up @@ -59,10 +53,11 @@ type Chain interface {

// GetMovedFundsSweepRequest gets the on-chain moved funds sweep request for
// the given moving funds transaction hash and output index.
// The returned bool value indicates whether the request was found or not.
GetMovedFundsSweepRequest(
movingFundsTxHash bitcoin.Hash,
movingFundsTxOutpointIndex uint32,
) (*tbtc.MovedFundsSweepRequest, error)
) (*tbtc.MovedFundsSweepRequest, bool, error)

// SubmitRedemptionProofWithReimbursement submits the redemption proof
// via MaintainerProxy. The caller is reimbursed.
Expand Down
6 changes: 3 additions & 3 deletions pkg/maintainer/spv/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,7 @@ func (lc *localChain) setMovedFundsSweepRequest(
func (lc *localChain) GetMovedFundsSweepRequest(
movingFundsTxHash bitcoin.Hash,
movingFundsTxOutpointIndex uint32,
) (*tbtc.MovedFundsSweepRequest, error) {
) (*tbtc.MovedFundsSweepRequest, bool, error) {
lc.mutex.Lock()
defer lc.mutex.Unlock()

Expand All @@ -677,10 +677,10 @@ func (lc *localChain) GetMovedFundsSweepRequest(

request, ok := lc.movedFundsSweepRequests[requestKey]
if !ok {
return nil, fmt.Errorf("request not found")
return nil, false, nil
}

return request, nil
return request, true, nil
}

type mockBlockCounter struct {
Expand Down
8 changes: 7 additions & 1 deletion pkg/maintainer/spv/moved_funds_sweep.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ func isUnprovenMovedFundsSweepTransaction(
requestTransactionHash := transaction.Inputs[0].Outpoint.TransactionHash
requestOutputIndex := transaction.Inputs[0].Outpoint.OutputIndex

movedFundsSweepRequest, err := spvChain.GetMovedFundsSweepRequest(
movedFundsSweepRequest, isRequest, err := spvChain.GetMovedFundsSweepRequest(
requestTransactionHash,
requestOutputIndex,
)
Expand All @@ -286,6 +286,12 @@ func isUnprovenMovedFundsSweepTransaction(
)
}

// Check if it's a moved funds sweep request at all.
if !isRequest {
return false, nil
}

// Check if it's a pending moved funds sweep request.
if movedFundsSweepRequest.State != tbtc.MovedFundsStatePending {
return false, nil
}
Expand Down
18 changes: 10 additions & 8 deletions pkg/tbtc/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,18 +209,20 @@ type BridgeChain interface {
) (*RedemptionRequest, bool, error)

// GetDepositRequest gets the on-chain deposit request for the given
// funding transaction hash and output index.The returned values represent:
// - deposit request which is non-nil only when the deposit request was
// found,
// - boolean value which is true if the deposit request was found, false
// otherwise,
// - error which is non-nil only when the function execution failed. It will
// be nil if the deposit request was not found, but the function execution
// succeeded.
// funding transaction hash and output index. The returned bool value
// indicates whether the request was found or not.
GetDepositRequest(
fundingTxHash bitcoin.Hash,
fundingOutputIndex uint32,
) (*DepositChainRequest, bool, error)

// GetMovedFundsSweepRequest gets the on-chain moved funds sweep request for
// the given moving funds transaction hash and output index.
// The returned bool value indicates whether the request was found or not.
GetMovedFundsSweepRequest(
movingFundsTxHash bitcoin.Hash,
movingFundsTxOutpointIndex uint32,
) (*MovedFundsSweepRequest, bool, error)
}

// NewWalletRegisteredEvent represents a new wallet registered event.
Expand Down
7 changes: 7 additions & 0 deletions pkg/tbtc/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,13 @@ func (lc *localChain) IsBetaOperator() (bool, error) {
panic("unsupported")
}

func (lc *localChain) GetMovedFundsSweepRequest(
movingFundsTxHash bitcoin.Hash,
movingFundsTxOutpointIndex uint32,
) (*MovedFundsSweepRequest, bool, error) {
panic("unsupported")
}

func (lc *localChain) GetOperatorID(
operatorAddress chain.Address,
) (chain.OperatorID, error) {
Expand Down
60 changes: 48 additions & 12 deletions pkg/tbtc/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import (
"crypto/elliptic"
"encoding/hex"
"fmt"
"golang.org/x/exp/slices"
"math/big"
"sync"
"time"

"golang.org/x/exp/slices"

"github.com/ipfs/go-log/v2"
"github.com/keep-network/keep-core/pkg/bitcoin"
"github.com/keep-network/keep-core/pkg/chain"
Expand Down Expand Up @@ -579,8 +580,15 @@ func EnsureWalletSyncedBetweenChains(
}

for _, utxo := range allUtxos {
// We know that valid first transaction of the wallet always
// have just one output. Any utxos with output index other
// The first valid transaction of a wallet is most likely a deposit
// sweep, but there is a small chance it could be a moved funds sweep.
// It could happen if the wallet was selected as a target wallet and
// some funds were moved to it by the source wallet. In that case the
// wallet could create a moved fund sweep transaction even before
// sweeping any deposits.

// In any case, we know that valid first transaction of the wallet
// always have just one output. Any utxos with output index other
// than 0 are certainly not produced by the wallet and, we should
// not take them into account.
if utxo.Outpoint.OutputIndex != 0 {
Expand All @@ -596,11 +604,10 @@ func EnsureWalletSyncedBetweenChains(
)
}

// We know that valid first transaction of the wallet have all their
// inputs referring to revealed deposits. We need to check just
// one input. If it points to a revealed deposit, that means
// the given transaction is produced by our wallet. Otherwise,
// such a transaction is a spam.
// The transaction could be a deposit sweep. In that case all the
// transaction's inputs must refer to revealed deposits. We can
// check one input. If it points to a revealed deposit, that means
// the given transaction is produced by our wallet.
input := transaction.Inputs[0]
_, isDeposit, err := bridgeChain.GetDepositRequest(
input.Outpoint.TransactionHash,
Expand All @@ -617,11 +624,40 @@ func EnsureWalletSyncedBetweenChains(
}

if isDeposit {
// If that's the case, the wallet was already done their
// first Bitcoin transaction and the Bridge is awaiting the
// SPV proof.
// If that's the case, the wallet has already created a deposit
// sweep as their first Bitcoin transaction and the Bridge is
// awaiting the SPV proof.
return fmt.Errorf("wallet already produced their first " +
"Bitcoin transaction (deposit sweep); Bridge is probably " +
"awaiting the SPV proof",
)
}

// The transaction could be a moved funds sweep request. In that
// case the transaction's input must refer to a moved funds sweep
// request. If the input points to a moved funds sweep request, that
// means the given transaction is produced by our wallet.
_, isRequest, err := bridgeChain.GetMovedFundsSweepRequest(
input.Outpoint.TransactionHash,
input.Outpoint.OutputIndex,
)
if err != nil {
return fmt.Errorf(
"cannot get moved funds sweep request for hash [%s] "+
"and output index [%v]: [%v]",
input.Outpoint.TransactionHash.String(),
input.Outpoint.OutputIndex,
err,
)
}

if isRequest {
// If that's the case, the wallet has already created a moved
// funds sweep as their first Bitcoin transaction and the Bridge
// is awaiting the SPV proof.
return fmt.Errorf("wallet already produced their first " +
"Bitcoin transaction; Bridge is probably awaiting the SPV proof",
"Bitcoin transaction (moved funds sweep); Bridge is " +
"probably awaiting the SPV proof",
)
}

Expand Down
7 changes: 0 additions & 7 deletions pkg/tbtcpg/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,6 @@ type Chain interface {
err error,
)

// GetMovedFundsSweepRequest gets the on-chain moved funds sweep request for
// the given moving funds transaction hash and output index.
GetMovedFundsSweepRequest(
movingFundsTxHash bitcoin.Hash,
movingFundsTxOutpointIndex uint32,
) (*tbtc.MovedFundsSweepRequest, error)

// PastMovingFundsCommitmentSubmittedEvents fetches past moving funds
// commitment submitted events according to the provided filter or
// unfiltered if the filter is nil. Returned events are sorted by the block
Expand Down
6 changes: 3 additions & 3 deletions pkg/tbtcpg/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,7 @@ func (lc *LocalChain) GetMovingFundsParameters() (
func (lc *LocalChain) GetMovedFundsSweepRequest(
movingFundsTxHash bitcoin.Hash,
movingFundsTxOutpointIndex uint32,
) (*tbtc.MovedFundsSweepRequest, error) {
) (*tbtc.MovedFundsSweepRequest, bool, error) {
lc.mutex.Lock()
defer lc.mutex.Unlock()

Expand All @@ -781,10 +781,10 @@ func (lc *LocalChain) GetMovedFundsSweepRequest(

request, ok := lc.movedFundsSweepRequests[requestKey]
if !ok {
return nil, fmt.Errorf("request not found")
return nil, false, nil
}

return request, nil
return request, true, nil
}

func (lc *LocalChain) SetMovedFundsSweepRequest(
Expand Down
8 changes: 7 additions & 1 deletion pkg/tbtcpg/moved_funds_sweep.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ func (mfst *MovedFundsSweepTask) FindMovingFundsTxData(
// Find the first outpoint that represents an unproven moved funds sweep
// request and return it.
for _, outpoint := range movingFundsTxOutpoints {
movedFundsSweepRequest, err := mfst.chain.GetMovedFundsSweepRequest(
movedFundsSweepRequest, isRequest, err := mfst.chain.GetMovedFundsSweepRequest(
outpoint.TransactionHash,
outpoint.OutputIndex,
)
Expand All @@ -185,6 +185,12 @@ func (mfst *MovedFundsSweepTask) FindMovingFundsTxData(
)
}

// Check just in case. It should not happen, as all the outpoint should
// represent a valid request.
if !isRequest {
continue
}

if movedFundsSweepRequest.State == tbtc.MovedFundsStatePending {
return outpoint.TransactionHash, outpoint.OutputIndex, nil
}
Expand Down
Loading