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

Release host utxos when formation, refresh, or renew fails #149

Merged
merged 1 commit into from
Jan 12, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
default: patch
---

# Release locked host UTXOs if contract formation, renewal, or refresh fails
38 changes: 33 additions & 5 deletions rhp/v4/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -566,13 +566,21 @@ func (s *Server) handleRPCFormContract(stream net.Conn) error {
})
}

var broadcast bool
// fund the host collateral
basis, toSign, err := s.wallet.FundV2Transaction(&formationTxn, hostCost, true)
if errors.Is(err, wallet.ErrNotEnoughFunds) {
return rhp4.ErrHostFundError
} else if err != nil {
return fmt.Errorf("failed to fund transaction: %w", err)
}
defer func() {
if broadcast {
return
}
// release the inputs if the transaction is not going to be broadcast
s.wallet.ReleaseInputs(nil, []types.V2Transaction{formationTxn})
}()
// sign the transaction inputs
s.wallet.SignV2Inputs(&formationTxn, toSign)
// send the host inputs to the renter
Expand Down Expand Up @@ -633,7 +641,6 @@ func (s *Server) handleRPCFormContract(stream net.Conn) error {
} else if _, err = s.chain.AddV2PoolTransactions(basis, formationSet); err != nil {
return errorBadRequest("failed to broadcast formation transaction: %v", err)
}
s.syncer.BroadcastV2TransactionSet(basis, formationSet)

// add the contract to the contractor
err = s.contractor.AddV2Contract(TransactionSet{
Expand All @@ -643,6 +650,9 @@ func (s *Server) handleRPCFormContract(stream net.Conn) error {
if err != nil {
return fmt.Errorf("failed to add contract: %w", err)
}
// broadcast the finalized contract formation set
broadcast = true // set broadcast so the UTXOs will not be released if the renter happens to disconnect before receiving the last response
s.syncer.BroadcastV2TransactionSet(basis, formationSet)

// send the finalized transaction set to the renter
return rhp4.WriteResponse(stream, &rhp4.RPCFormContractThirdResponse{
Expand Down Expand Up @@ -713,12 +723,20 @@ func (s *Server) handleRPCRefreshContract(stream net.Conn) error {
return fmt.Errorf("failed to get contract element: %w", err)
}

var broadcast bool
basis, toSign, err := s.wallet.FundV2Transaction(&renewalTxn, hostCost, true)
if errors.Is(err, wallet.ErrNotEnoughFunds) {
return rhp4.ErrHostFundError
} else if err != nil {
return fmt.Errorf("failed to fund transaction: %w", err)
}
defer func() {
if broadcast {
return
}
// release the locked UTXOs if the transaction is not going to be broadcast
s.wallet.ReleaseInputs(nil, []types.V2Transaction{renewalTxn})
}()

// update renter inputs to reflect our chain state
if basis != req.Basis {
Expand Down Expand Up @@ -799,8 +817,6 @@ func (s *Server) handleRPCRefreshContract(stream net.Conn) error {
} else if _, err = s.chain.AddV2PoolTransactions(basis, renewalSet); err != nil {
return errorBadRequest("failed to broadcast renewal set: %v", err)
}
// broadcast the transaction set
s.syncer.BroadcastV2TransactionSet(basis, renewalSet)

// add the contract to the contractor
err = s.contractor.RenewV2Contract(TransactionSet{
Expand All @@ -811,6 +827,9 @@ func (s *Server) handleRPCRefreshContract(stream net.Conn) error {
return fmt.Errorf("failed to add contract: %w", err)
}

broadcast = true // set broadcast so the UTXOs will not be released if the renter happens to disconnect before receiving the last response
s.syncer.BroadcastV2TransactionSet(basis, renewalSet)

// send the finalized transaction set to the renter
return rhp4.WriteResponse(stream, &rhp4.RPCRefreshContractThirdResponse{
Basis: basis,
Expand Down Expand Up @@ -882,12 +901,20 @@ func (s *Server) handleRPCRenewContract(stream net.Conn) error {
return fmt.Errorf("failed to get contract element: %w", err)
}

var broadcast bool
basis, toSign, err := s.wallet.FundV2Transaction(&renewalTxn, hostCost, true)
if errors.Is(err, wallet.ErrNotEnoughFunds) {
return rhp4.ErrHostFundError
} else if err != nil {
return fmt.Errorf("failed to fund transaction: %w", err)
}
defer func() {
if broadcast {
return
}
// release the locked UTXOs if the transaction is not going to be broadcast
s.wallet.ReleaseInputs(nil, []types.V2Transaction{renewalTxn})
}()

// update renter inputs to reflect our chain state
if basis != req.Basis {
Expand Down Expand Up @@ -968,8 +995,6 @@ func (s *Server) handleRPCRenewContract(stream net.Conn) error {
} else if _, err = s.chain.AddV2PoolTransactions(basis, renewalSet); err != nil {
return errorBadRequest("failed to broadcast renewal set: %v", err)
}
// broadcast the transaction set
s.syncer.BroadcastV2TransactionSet(basis, renewalSet)

// add the contract to the contractor
err = s.contractor.RenewV2Contract(TransactionSet{
Expand All @@ -980,6 +1005,9 @@ func (s *Server) handleRPCRenewContract(stream net.Conn) error {
return fmt.Errorf("failed to add contract: %w", err)
}

broadcast = true // set broadcast so the UTXOs will not be released if the renter happens to disconnect before receiving the last response
s.syncer.BroadcastV2TransactionSet(basis, renewalSet)

// send the finalized transaction set to the renter
return rhp4.WriteResponse(stream, &rhp4.RPCRenewContractThirdResponse{
Basis: basis,
Expand Down
Loading