From 09d33c20cf2daf21bf5aab7e201158fd57ceb823 Mon Sep 17 00:00:00 2001 From: mike76-dev Date: Fri, 10 Jan 2025 19:10:37 +0100 Subject: [PATCH 1/2] Release inputs if RPCFormContract, RPCRenewContract, or RPCRefreshContract fail --- rhp/v4/rpc.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/rhp/v4/rpc.go b/rhp/v4/rpc.go index 1600453..5782545 100644 --- a/rhp/v4/rpc.go +++ b/rhp/v4/rpc.go @@ -568,11 +568,13 @@ func RPCFormContract(ctx context.Context, t TransportClient, tp TxPool, signer F RenterParents: formationSet, } if err := rhp4.WriteRequest(s, rhp4.RPCFormContractID, &req); err != nil { + signer.ReleaseInputs([]types.V2Transaction{formationTxn}) return RPCFormContractResult{}, fmt.Errorf("failed to write request: %w", err) } var hostInputsResp rhp4.RPCFormContractResponse if err := rhp4.ReadResponse(s, &hostInputsResp); err != nil { + signer.ReleaseInputs([]types.V2Transaction{formationTxn}) return RPCFormContractResult{}, fmt.Errorf("failed to read host inputs response: %w", err) } @@ -584,6 +586,7 @@ func RPCFormContract(ctx context.Context, t TransportClient, tp TxPool, signer F } if n := hostInputSum.Cmp(fc.TotalCollateral); n < 0 { + signer.ReleaseInputs([]types.V2Transaction{formationTxn}) return RPCFormContractResult{}, fmt.Errorf("expected host to fund at least %v, got %v", fc.TotalCollateral, hostInputSum) } else if n > 0 { // add change output @@ -606,12 +609,14 @@ func RPCFormContract(ctx context.Context, t TransportClient, tp TxPool, signer F } // send the renter signatures if err := rhp4.WriteResponse(s, &renterPolicyResp); err != nil { + signer.ReleaseInputs([]types.V2Transaction{formationTxn}) return RPCFormContractResult{}, fmt.Errorf("failed to write signature response: %w", err) } // read the finalized transaction set var hostTransactionSetResp rhp4.RPCFormContractThirdResponse if err := rhp4.ReadResponse(s, &hostTransactionSetResp); err != nil { + // at this point the formation txn must already have been broadcast, so no need to release inputs return RPCFormContractResult{}, fmt.Errorf("failed to read final response: %w", err) } @@ -681,6 +686,7 @@ func RPCRenewContract(ctx context.Context, t TransportClient, tp TxPool, signer req.Basis, req.RenterParents, err = tp.V2TransactionSet(basis, renewalTxn) if err != nil { + signer.ReleaseInputs([]types.V2Transaction{renewalTxn}) return RPCRenewContractResult{}, fmt.Errorf("failed to get transaction set: %w", err) } for _, si := range renewalTxn.SiacoinInputs { @@ -695,11 +701,13 @@ func RPCRenewContract(ctx context.Context, t TransportClient, tp TxPool, signer defer s.Close() if err := rhp4.WriteRequest(s, rhp4.RPCRenewContractID, &req); err != nil { + signer.ReleaseInputs([]types.V2Transaction{renewalTxn}) return RPCRenewContractResult{}, fmt.Errorf("failed to write request: %w", err) } var hostInputsResp rhp4.RPCRenewContractResponse if err := rhp4.ReadResponse(s, &hostInputsResp); err != nil { + signer.ReleaseInputs([]types.V2Transaction{renewalTxn}) return RPCRenewContractResult{}, fmt.Errorf("failed to read host inputs response: %w", err) } @@ -712,6 +720,7 @@ func RPCRenewContract(ctx context.Context, t TransportClient, tp TxPool, signer // verify the host added enough inputs if n := hostInputSum.Cmp(hostCost); n < 0 { + signer.ReleaseInputs([]types.V2Transaction{renewalTxn}) return RPCRenewContractResult{}, fmt.Errorf("expected host to fund %v, got %v", hostCost, hostInputSum) } else if n > 0 { // add change output @@ -739,12 +748,14 @@ func RPCRenewContract(ctx context.Context, t TransportClient, tp TxPool, signer renterPolicyResp.RenterSatisfiedPolicies = append(renterPolicyResp.RenterSatisfiedPolicies, si.SatisfiedPolicy) } if err := rhp4.WriteResponse(s, &renterPolicyResp); err != nil { + signer.ReleaseInputs([]types.V2Transaction{renewalTxn}) return RPCRenewContractResult{}, fmt.Errorf("failed to write signature response: %w", err) } // read the finalized transaction set var hostTransactionSetResp rhp4.RPCRenewContractThirdResponse if err := rhp4.ReadResponse(s, &hostTransactionSetResp); err != nil { + // at this point the renewal txn must already have been broadcast, so no need to release inputs return RPCRenewContractResult{}, fmt.Errorf("failed to read final response: %w", err) } @@ -812,6 +823,7 @@ func RPCRefreshContract(ctx context.Context, t TransportClient, tp TxPool, signe req.Basis, req.RenterParents, err = tp.V2TransactionSet(basis, renewalTxn) if err != nil { + signer.ReleaseInputs([]types.V2Transaction{renewalTxn}) return RPCRefreshContractResult{}, fmt.Errorf("failed to get transaction set: %w", err) } for _, si := range renewalTxn.SiacoinInputs { @@ -826,11 +838,13 @@ func RPCRefreshContract(ctx context.Context, t TransportClient, tp TxPool, signe defer s.Close() if err := rhp4.WriteRequest(s, rhp4.RPCRefreshContractID, &req); err != nil { + signer.ReleaseInputs([]types.V2Transaction{renewalTxn}) return RPCRefreshContractResult{}, fmt.Errorf("failed to write request: %w", err) } var hostInputsResp rhp4.RPCRefreshContractResponse if err := rhp4.ReadResponse(s, &hostInputsResp); err != nil { + signer.ReleaseInputs([]types.V2Transaction{renewalTxn}) return RPCRefreshContractResult{}, fmt.Errorf("failed to read host inputs response: %w", err) } @@ -843,6 +857,7 @@ func RPCRefreshContract(ctx context.Context, t TransportClient, tp TxPool, signe // verify the host added enough inputs if n := hostInputSum.Cmp(hostCost); n < 0 { + signer.ReleaseInputs([]types.V2Transaction{renewalTxn}) return RPCRefreshContractResult{}, fmt.Errorf("expected host to fund %v, got %v", hostCost, hostInputSum) } else if n > 0 { // add change output @@ -870,12 +885,14 @@ func RPCRefreshContract(ctx context.Context, t TransportClient, tp TxPool, signe renterPolicyResp.RenterSatisfiedPolicies = append(renterPolicyResp.RenterSatisfiedPolicies, si.SatisfiedPolicy) } if err := rhp4.WriteResponse(s, &renterPolicyResp); err != nil { + signer.ReleaseInputs([]types.V2Transaction{renewalTxn}) return RPCRefreshContractResult{}, fmt.Errorf("failed to write signature response: %w", err) } // read the finalized transaction set var hostTransactionSetResp rhp4.RPCRefreshContractThirdResponse if err := rhp4.ReadResponse(s, &hostTransactionSetResp); err != nil { + // at this point the renewal txn must already have been broadcast, so no need to release inputs return RPCRefreshContractResult{}, fmt.Errorf("failed to read final response: %w", err) } From 54a0611351ed918740885e6f24fc9161ed19b4be Mon Sep 17 00:00:00 2001 From: Nate Date: Fri, 10 Jan 2025 11:01:45 -0800 Subject: [PATCH 2/2] chore: document change --- ...d_utxos_if_contract_formation_renewal_or_refresh_fails.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/release_locked_utxos_if_contract_formation_renewal_or_refresh_fails.md diff --git a/.changeset/release_locked_utxos_if_contract_formation_renewal_or_refresh_fails.md b/.changeset/release_locked_utxos_if_contract_formation_renewal_or_refresh_fails.md new file mode 100644 index 0000000..0f198e3 --- /dev/null +++ b/.changeset/release_locked_utxos_if_contract_formation_renewal_or_refresh_fails.md @@ -0,0 +1,5 @@ +--- +default: patch +--- + +# Release locked UTXOs if contract formation, renewal, or refresh fails