Skip to content

Commit

Permalink
Moved funds sweep SPV proofs (#3793)
Browse files Browse the repository at this point in the history
#Refs: #3738.
This PR adds functionalities for creating and submitting moved funds
sweep transaction SPV proofs.

Finding the possible unproven moved funds sweep transactions is done by
retrieving `MovingFundsCommitmentSubmitted` events and looking at the
recent transactions of target wallets from each event.

An unproven moved funds seep transaction must have the following format:
- the first input must refer to an unproven moved funds sweep request
- the second input (optional) must refer to the current wallet's main
UTXO
- the (single) output that pays to the wallet itself

Once the unproven moved funds sweep transactions are found, they are
proven.
  • Loading branch information
lukasz-zimnoch authored Mar 15, 2024
2 parents f1c4f08 + ddfd968 commit a1c6c31
Show file tree
Hide file tree
Showing 8 changed files with 888 additions and 3 deletions.
51 changes: 51 additions & 0 deletions pkg/chain/ethereum/tbtc.go
Original file line number Diff line number Diff line change
Expand Up @@ -1820,6 +1820,57 @@ func (tc *TbtcChain) SubmitMovingFundsProofWithReimbursement(
return err
}

func (tc *TbtcChain) SubmitMovedFundsSweepProofWithReimbursement(
transaction *bitcoin.Transaction,
proof *bitcoin.SpvProof,
mainUTXO bitcoin.UnspentTransactionOutput,
) error {
bitcoinTxInfo := tbtcabi.BitcoinTxInfo3{
Version: transaction.SerializeVersion(),
InputVector: transaction.SerializeInputs(),
OutputVector: transaction.SerializeOutputs(),
Locktime: transaction.SerializeLocktime(),
}
movedFundsSweepProof := tbtcabi.BitcoinTxProof2{
MerkleProof: proof.MerkleProof,
TxIndexInBlock: big.NewInt(int64(proof.TxIndexInBlock)),
BitcoinHeaders: proof.BitcoinHeaders,
CoinbasePreimage: proof.CoinbasePreimage,
CoinbaseProof: proof.CoinbaseProof,
}
utxo := tbtcabi.BitcoinTxUTXO2{
TxHash: mainUTXO.Outpoint.TransactionHash,
TxOutputIndex: mainUTXO.Outpoint.OutputIndex,
TxOutputValue: uint64(mainUTXO.Value),
}

gasEstimate, err := tc.maintainerProxy.SubmitMovedFundsSweepProofGasEstimate(
bitcoinTxInfo,
movedFundsSweepProof,
utxo,
)
if err != nil {
return err
}

// The original estimate for this contract call is too low and the call
// fails on reimbursing the submitter. Example:
// 0xe27a92883e0e64da8a3a54a15a260ea2f4d3d48470129ac5c09bfe9637d7e114
// Here we add a 20% margin to overcome the gas problems.
gasEstimateWithMargin := float64(gasEstimate) * float64(1.2)

_, err = tc.maintainerProxy.SubmitMovedFundsSweepProof(
bitcoinTxInfo,
movedFundsSweepProof,
utxo,
ethutil.TransactionOptions{
GasLimit: uint64(gasEstimateWithMargin),
},
)

return err
}

func (tc *TbtcChain) ValidateMovedFundsSweepProposal(
walletPublicKeyHash [20]byte,
proposal *tbtc.MovedFundsSweepProposal,
Expand Down
15 changes: 15 additions & 0 deletions pkg/maintainer/spv/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ type Chain interface {
redeemerOutputScript bitcoin.Script,
) (*tbtc.RedemptionRequest, bool, 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)

// SubmitRedemptionProofWithReimbursement submits the redemption proof
// via MaintainerProxy. The caller is reimbursed.
SubmitRedemptionProofWithReimbursement(
Expand All @@ -75,6 +82,14 @@ type Chain interface {
walletPublicKeyHash [20]byte,
) error

// SubmitMovedFundsSweepProofWithReimbursement submits the moved funds sweep
// proof via MaintainerProxy. The caller is reimbursed.
SubmitMovedFundsSweepProofWithReimbursement(
transaction *bitcoin.Transaction,
proof *bitcoin.SpvProof,
mainUTXO bitcoin.UnspentTransactionOutput,
) error

// PastDepositRevealedEvents fetches past deposit reveal 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
Expand Down
87 changes: 87 additions & 0 deletions pkg/maintainer/spv/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,24 @@ type submittedMovingFundsProof struct {
walletPublicKeyHash [20]byte
}

type submittedMovedFundsSweepProof struct {
transaction *bitcoin.Transaction
proof *bitcoin.SpvProof
mainUTXO bitcoin.UnspentTransactionOutput
}

type localChain struct {
mutex sync.Mutex

blockCounter chain.BlockCounter
wallets map[[20]byte]*tbtc.WalletChainData
depositRequests map[[32]byte]*tbtc.DepositChainRequest
pendingRedemptionRequests map[[32]byte]*tbtc.RedemptionRequest
movedFundsSweepRequests map[[32]byte]*tbtc.MovedFundsSweepRequest
submittedRedemptionProofs []*submittedRedemptionProof
submittedDepositSweepProofs []*submittedDepositSweepProof
submittedMovingFundsProofs []*submittedMovingFundsProof
submittedMovedFundsSweepProofs []*submittedMovedFundsSweepProof
pastRedemptionRequestedEvents map[[32]byte][]*tbtc.RedemptionRequestedEvent
pastDepositRevealedEvents map[[32]byte][]*tbtc.DepositRevealedEvent
pastMovingFundsCommitmentSubmittedEvents map[[32]byte][]*tbtc.MovingFundsCommitmentSubmittedEvent
Expand All @@ -62,6 +70,7 @@ func newLocalChain() *localChain {
wallets: make(map[[20]byte]*tbtc.WalletChainData),
depositRequests: make(map[[32]byte]*tbtc.DepositChainRequest),
pendingRedemptionRequests: make(map[[32]byte]*tbtc.RedemptionRequest),
movedFundsSweepRequests: make(map[[32]byte]*tbtc.MovedFundsSweepRequest),
submittedRedemptionProofs: make([]*submittedRedemptionProof, 0),
submittedDepositSweepProofs: make([]*submittedDepositSweepProof, 0),
submittedMovingFundsProofs: make([]*submittedMovingFundsProof, 0),
Expand Down Expand Up @@ -304,6 +313,33 @@ func (lc *localChain) getSubmittedMovingFundsProofs() []*submittedMovingFundsPro
return lc.submittedMovingFundsProofs
}

func (lc *localChain) SubmitMovedFundsSweepProofWithReimbursement(
transaction *bitcoin.Transaction,
proof *bitcoin.SpvProof,
mainUTXO bitcoin.UnspentTransactionOutput,
) error {
lc.mutex.Lock()
defer lc.mutex.Unlock()

lc.submittedMovedFundsSweepProofs = append(
lc.submittedMovedFundsSweepProofs,
&submittedMovedFundsSweepProof{
transaction: transaction,
proof: proof,
mainUTXO: mainUTXO,
},
)

return nil
}

func (lc *localChain) getSubmittedMovedFundsSweepProofs() []*submittedMovedFundsSweepProof {
lc.mutex.Lock()
defer lc.mutex.Unlock()

return lc.submittedMovedFundsSweepProofs
}

func (lc *localChain) Ready() (bool, error) {
panic("unsupported")
}
Expand Down Expand Up @@ -596,6 +632,57 @@ func buildPastMovingFundsCommitmentSubmittedEventsKey(
return sha256.Sum256(buffer.Bytes()), nil
}

func buildMovedFundsSweepRequestKey(
movingFundsTxHash bitcoin.Hash,
movingFundsTxOutpointIndex uint32,
) [32]byte {
var buffer bytes.Buffer

buffer.Write(movingFundsTxHash[:])

outputIndex := make([]byte, 4)
binary.BigEndian.PutUint32(outputIndex, movingFundsTxOutpointIndex)
buffer.Write(outputIndex)

return sha256.Sum256(buffer.Bytes())
}

func (lc *localChain) setMovedFundsSweepRequest(
movingFundsTxHash bitcoin.Hash,
movingFundsTxOutpointIndex uint32,
request *tbtc.MovedFundsSweepRequest,
) {
lc.mutex.Lock()
defer lc.mutex.Unlock()

requestKey := buildMovedFundsSweepRequestKey(
movingFundsTxHash,
movingFundsTxOutpointIndex,
)

lc.movedFundsSweepRequests[requestKey] = request
}

func (lc *localChain) GetMovedFundsSweepRequest(
movingFundsTxHash bitcoin.Hash,
movingFundsTxOutpointIndex uint32,
) (*tbtc.MovedFundsSweepRequest, error) {
lc.mutex.Lock()
defer lc.mutex.Unlock()

requestKey := buildMovedFundsSweepRequestKey(
movingFundsTxHash,
movingFundsTxOutpointIndex,
)

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

return request, nil
}

type mockBlockCounter struct {
mutex sync.Mutex
currentBlock uint64
Expand Down
Loading

0 comments on commit a1c6c31

Please sign in to comment.