Skip to content

Commit

Permalink
Merge pull request #821 from SiaFoundation/chris/fix-refresh-panic
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisSchinnerl authored Dec 14, 2023
2 parents 3b489f1 + ab1f391 commit dcafaf8
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 28 deletions.
6 changes: 3 additions & 3 deletions api/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,9 @@ func (c Contract) RenterFunds() types.Currency {
}

// RemainingCollateral returns the remaining collateral in the contract.
func (c Contract) RemainingCollateral(s rhpv2.HostSettings) types.Currency {
if c.Revision.MissedHostPayout().Cmp(s.ContractPrice) < 0 {
func (c Contract) RemainingCollateral() types.Currency {
if c.Revision.MissedHostPayout().Cmp(c.ContractPrice) < 0 {
return types.ZeroCurrency
}
return c.Revision.MissedHostPayout().Sub(s.ContractPrice)
return c.Revision.MissedHostPayout().Sub(c.ContractPrice)
}
36 changes: 29 additions & 7 deletions autopilot/contractor.go
Original file line number Diff line number Diff line change
Expand Up @@ -1441,10 +1441,15 @@ func (c *contractor) refreshContract(ctx context.Context, w Worker, ci contractI
}

// calculate the renter funds
renterFunds, err := c.refreshFundingEstimate(ctx, state.cfg, ci, state.fee)
if err != nil {
c.logger.Errorw(fmt.Sprintf("could not get refresh funding estimate, err: %v", err), "hk", hk, "fcid", fcid)
return api.ContractMetadata{}, true, err
var renterFunds types.Currency
if isOutOfFunds(state.cfg, ci.settings, ci.contract) {
renterFunds, err = c.refreshFundingEstimate(ctx, state.cfg, ci, state.fee)
if err != nil {
c.logger.Errorw(fmt.Sprintf("could not get refresh funding estimate, err: %v", err), "hk", hk, "fcid", fcid)
return api.ContractMetadata{}, true, err
}
} else {
renterFunds = rev.ValidRenterPayout() // don't increase funds
}

// check our budget
Expand All @@ -1453,14 +1458,30 @@ func (c *contractor) refreshContract(ctx context.Context, w Worker, ci contractI
return api.ContractMetadata{}, false, fmt.Errorf("insufficient budget: %s < %s", budget.String(), renterFunds.String())
}

// calculate the new collateral
expectedStorage := renterFundsToExpectedStorage(renterFunds, contract.EndHeight()-cs.BlockHeight, ci.priceTable)
unallocatedCollateral := rev.MissedHostPayout().Sub(contract.ContractPrice)
minNewCollateral := minNewCollateral(unallocatedCollateral)

// calculate the expected new collateral to determine the minNewCollateral.
// If the contract isn't below the min collateral, we don't enforce a
// minimum.
var minNewColl types.Currency
_, _, expectedNewCollateral := rhpv3.RenewalCosts(contract.Revision.FileContract, ci.priceTable, expectedStorage, contract.EndHeight())
if isBelowCollateralThreshold(expectedNewCollateral, unallocatedCollateral) {
minNewColl = minNewCollateral(unallocatedCollateral)
}

// renew the contract
resp, err := w.RHPRenew(ctx, contract.ID, contract.EndHeight(), hk, contract.SiamuxAddr, settings.Address, state.address, renterFunds, minNewCollateral, expectedStorage, settings.WindowSize)
resp, err := w.RHPRenew(ctx, contract.ID, contract.EndHeight(), hk, contract.SiamuxAddr, settings.Address, state.address, renterFunds, minNewColl, expectedStorage, settings.WindowSize)
if err != nil {
if strings.Contains(err.Error(), "new collateral is too low") {
c.logger.Debugw("refresh failed: contract wouldn't have enough collateral after refresh",
"hk", hk,
"fcid", fcid,
"unallocatedCollateral", unallocatedCollateral.String(),
"minNewCollateral", minNewColl.String(),
)
return api.ContractMetadata{}, true, err
}
c.logger.Errorw("refresh failed", zap.Error(err), "hk", hk, "fcid", fcid)
if strings.Contains(err.Error(), wallet.ErrInsufficientBalance.Error()) {
return api.ContractMetadata{}, false, err
Expand All @@ -1484,6 +1505,7 @@ func (c *contractor) refreshContract(ctx context.Context, w Worker, ci contractI
"fcid", refreshedContract.ID,
"renewedFrom", contract.ID,
"renterFunds", renterFunds.String(),
"minNewCollateral", minNewColl.String(),
"newCollateral", newCollateral.String(),
)
return refreshedContract, true, nil
Expand Down
2 changes: 1 addition & 1 deletion autopilot/hostfilter.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ func isOutOfCollateral(c api.Contract, s rhpv2.HostSettings, pt rhpv3.HostPriceT
expectedStorage = s.RemainingStorage
}
_, _, newCollateral := rhpv3.RenewalCosts(c.Revision.FileContract, pt, expectedStorage, c.EndHeight())
return isBelowCollateralThreshold(newCollateral, c.RemainingCollateral(s))
return isBelowCollateralThreshold(newCollateral, c.RemainingCollateral())
}

// isBelowCollateralThreshold returns true if the remainingCollateral is below a
Expand Down
2 changes: 1 addition & 1 deletion cmd/renterd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ func main() {

flag.Parse()

log.Println("renterd v0.6.0")
log.Println("renterd v0.7.1")
log.Println("Network", build.NetworkName())
if flag.Arg(0) == "version" {
log.Println("Commit:", githash)
Expand Down
3 changes: 1 addition & 2 deletions worker/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,7 @@ func (h *host) RenewContract(ctx context.Context, rrr api.RHPRenewRequest) (_ rh
// for it.
_, err = RPCLatestRevision(ctx, t, h.fcid, func(revision *types.FileContractRevision) (rhpv3.HostPriceTable, rhpv3.PaymentMethod, error) {
// Renew contract.
contractPrice = pt.ContractPrice
rev, txnSet, renewErr = RPCRenew(ctx, rrr, h.bus, t, pt, *revision, h.renterKey, h.logger)
rev, txnSet, contractPrice, renewErr = RPCRenew(ctx, rrr, h.bus, t, pt, *revision, h.renterKey, h.logger)
return rhpv3.HostPriceTable{}, nil, nil
})
return err
Expand Down
28 changes: 14 additions & 14 deletions worker/rhpv3.go
Original file line number Diff line number Diff line change
Expand Up @@ -1121,11 +1121,11 @@ func RPCAppendSector(ctx context.Context, t *transportV3, renterKey types.Privat
return
}

func RPCRenew(ctx context.Context, rrr api.RHPRenewRequest, bus Bus, t *transportV3, pt *rhpv3.HostPriceTable, rev types.FileContractRevision, renterKey types.PrivateKey, l *zap.SugaredLogger) (_ rhpv2.ContractRevision, _ []types.Transaction, err error) {
func RPCRenew(ctx context.Context, rrr api.RHPRenewRequest, bus Bus, t *transportV3, pt *rhpv3.HostPriceTable, rev types.FileContractRevision, renterKey types.PrivateKey, l *zap.SugaredLogger) (_ rhpv2.ContractRevision, _ []types.Transaction, _ types.Currency, err error) {
defer wrapErr(&err, "RPCRenew")
s, err := t.DialStream(ctx)
if err != nil {
return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to dial stream: %w", err)
return rhpv2.ContractRevision{}, nil, types.ZeroCurrency, fmt.Errorf("failed to dial stream: %w", err)
}
defer s.Close()

Expand All @@ -1135,36 +1135,36 @@ func RPCRenew(ctx context.Context, rrr api.RHPRenewRequest, bus Bus, t *transpor
ptUID = pt.UID
}
if err = s.WriteRequest(rhpv3.RPCRenewContractID, &ptUID); err != nil {
return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to send ptUID: %w", err)
return rhpv2.ContractRevision{}, nil, types.Currency{}, fmt.Errorf("failed to send ptUID: %w", err)
}

// If we didn't have a valid pricetable, read the temporary one from the
// host.
if ptUID == (rhpv3.SettingsID{}) {
var ptResp rhpv3.RPCUpdatePriceTableResponse
if err = s.ReadResponse(&ptResp, defaultRPCResponseMaxSize); err != nil {
return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to read RPCUpdatePriceTableResponse: %w", err)
return rhpv2.ContractRevision{}, nil, types.Currency{}, fmt.Errorf("failed to read RPCUpdatePriceTableResponse: %w", err)
}
pt = new(rhpv3.HostPriceTable)
if err = json.Unmarshal(ptResp.PriceTableJSON, pt); err != nil {
return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to unmarshal price table: %w", err)
return rhpv2.ContractRevision{}, nil, types.Currency{}, fmt.Errorf("failed to unmarshal price table: %w", err)
}
}

// Perform gouging checks.
gc, err := GougingCheckerFromContext(ctx, false)
if err != nil {
return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to get gouging checker: %w", err)
return rhpv2.ContractRevision{}, nil, types.Currency{}, fmt.Errorf("failed to get gouging checker: %w", err)
}
if breakdown := gc.Check(nil, pt); breakdown.Gouging() {
return rhpv2.ContractRevision{}, nil, fmt.Errorf("host gouging during renew: %v", breakdown)
return rhpv2.ContractRevision{}, nil, types.Currency{}, fmt.Errorf("host gouging during renew: %v", breakdown)
}

// Prepare the signed transaction that contains the final revision as well
// as the new contract
wprr, err := bus.WalletPrepareRenew(ctx, rev, rrr.HostAddress, rrr.RenterAddress, renterKey, rrr.RenterFunds, rrr.MinNewCollateral, *pt, rrr.EndHeight, rrr.WindowSize, rrr.ExpectedNewStorage)
if err != nil {
return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to prepare renew: %w", err)
return rhpv2.ContractRevision{}, nil, types.Currency{}, fmt.Errorf("failed to prepare renew: %w", err)
}

// Starting from here, we need to make sure to release the txn on error.
Expand All @@ -1187,13 +1187,13 @@ func RPCRenew(ctx context.Context, rrr api.RHPRenewRequest, bus Bus, t *transpor
FinalRevisionSignature: finalRevisionSignature,
}
if err = s.WriteResponse(&req); err != nil {
return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to send RPCRenewContractRequest: %w", err)
return rhpv2.ContractRevision{}, nil, types.Currency{}, fmt.Errorf("failed to send RPCRenewContractRequest: %w", err)
}

// Incorporate the host's additions.
var hostAdditions rhpv3.RPCRenewContractHostAdditions
if err = s.ReadResponse(&hostAdditions, defaultRPCResponseMaxSize); err != nil {
return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to read RPCRenewContractHostAdditions: %w", err)
return rhpv2.ContractRevision{}, nil, types.Currency{}, fmt.Errorf("failed to read RPCRenewContractHostAdditions: %w", err)
}
parents = append(parents, hostAdditions.Parents...)
txn.SiacoinInputs = append(txn.SiacoinInputs, hostAdditions.SiacoinInputs...)
Expand Down Expand Up @@ -1225,7 +1225,7 @@ func RPCRenew(ctx context.Context, rrr api.RHPRenewRequest, bus Bus, t *transpor
Signatures: []uint64{0, 1},
}
if err := bus.WalletSign(ctx, &txn, wprr.ToSign, cf); err != nil {
return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to sign transaction: %w", err)
return rhpv2.ContractRevision{}, nil, types.Currency{}, fmt.Errorf("failed to sign transaction: %w", err)
}

// Create a new no-op revision and sign it.
Expand All @@ -1249,13 +1249,13 @@ func RPCRenew(ctx context.Context, rrr api.RHPRenewRequest, bus Bus, t *transpor
RevisionSignature: renterNoOpRevisionSignature,
}
if err = s.WriteResponse(&rs); err != nil {
return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to send RPCRenewSignatures: %w", err)
return rhpv2.ContractRevision{}, nil, types.Currency{}, fmt.Errorf("failed to send RPCRenewSignatures: %w", err)
}

// Receive the host's signatures.
var hostSigs rhpv3.RPCRenewSignatures
if err = s.ReadResponse(&hostSigs, defaultRPCResponseMaxSize); err != nil {
return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to read RPCRenewSignatures: %w", err)
return rhpv2.ContractRevision{}, nil, types.Currency{}, fmt.Errorf("failed to read RPCRenewSignatures: %w", err)
}
txn.Signatures = append(txn.Signatures, hostSigs.TransactionSignatures...)

Expand All @@ -1265,7 +1265,7 @@ func RPCRenew(ctx context.Context, rrr api.RHPRenewRequest, bus Bus, t *transpor
return rhpv2.ContractRevision{
Revision: noOpRevision,
Signatures: [2]types.TransactionSignature{renterNoOpRevisionSignature, hostSigs.RevisionSignature},
}, txnSet, nil
}, txnSet, pt.ContractPrice, nil
}

// initialRevision returns the first revision of a file contract formation
Expand Down

0 comments on commit dcafaf8

Please sign in to comment.