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

Fix underflow when broadcasting storage proof for expired contracts #132

Merged
merged 4 commits into from
Aug 2, 2023
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
6 changes: 1 addition & 5 deletions host/contracts/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,6 @@ func (cm *ContractManager) handleContractAction(id types.FileContractID, height
validPayout, missedPayout := contract.Revision.ValidHostPayout(), contract.Revision.MissedHostPayout()
if missedPayout.Cmp(validPayout) >= 0 {
log.Info("skipping storage proof, no benefit to host", zap.String("validPayout", validPayout.ExactString()), zap.String("missedPayout", missedPayout.ExactString()))
if err := cm.store.ExpireContract(id, ContractStatusSuccessful); err != nil {
log.Error("failed to set contract status", zap.Error(err))
}
return
}

Expand Down Expand Up @@ -244,11 +241,10 @@ func (cm *ContractManager) handleContractAction(id types.FileContractID, height
log.Error("contract failed, revenue lost", zap.Uint64("windowStart", contract.Revision.WindowStart), zap.Uint64("windowEnd", contract.Revision.WindowEnd), zap.String("validPayout", validPayout.ExactString()), zap.String("missedPayout", missedPayout.ExactString()))
return
}
// note: this should always be a no-op, but it's good to be explicit
if err := cm.store.ExpireContract(id, ContractStatusSuccessful); err != nil {
log.Error("failed to set contract status", zap.Error(err))
}
log.Info("contract expired")
log.Info("contract successful", zap.String("validPayout", validPayout.ExactString()), zap.String("missedPayout", missedPayout.ExactString()))
default:
log.Panic("unrecognized contract action", zap.Stack("stack"))
}
Expand Down
49 changes: 30 additions & 19 deletions persist/sqlite/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,18 +92,24 @@ func (u *updateContractsTxn) ConfirmResolution(id types.FileContractID, height u
var dbID int64
if err := u.tx.QueryRow(query, height, sqlHash256(id)).Scan(&dbID); err != nil {
return fmt.Errorf("failed to confirm resolution: %w", err)
} else if err := setContractStatus(u.tx, id, contracts.ContractStatusSuccessful); err != nil {
return fmt.Errorf("failed to set contract status to ended: %w", err)
}

// reduce the host's locked and risked collateral
contract, err := getContract(u.tx, id)
if err != nil {
return fmt.Errorf("failed to get contract: %w", err)
} else if err := incrementCurrencyStat(u.tx, metricLockedCollateral, contract.LockedCollateral, true, time.Now()); err != nil {
return fmt.Errorf("failed to increment locked collateral stat: %w", err)
} else if err := incrementCurrencyStat(u.tx, metricRiskedCollateral, contract.Usage.RiskedCollateral, true, time.Now()); err != nil {
return fmt.Errorf("failed to increment risked collateral stat: %w", err)
}
// rejected, successful, and failed contracts have already had their
// collateral and revenue removed
if contract.Status == contracts.ContractStatusActive || contract.Status == contracts.ContractStatusPending {
if err := incrementCurrencyStat(u.tx, metricLockedCollateral, contract.LockedCollateral, true, time.Now()); err != nil {
return fmt.Errorf("failed to increment locked collateral stat: %w", err)
} else if err := incrementCurrencyStat(u.tx, metricRiskedCollateral, contract.Usage.RiskedCollateral, true, time.Now()); err != nil {
return fmt.Errorf("failed to increment risked collateral stat: %w", err)
}
}
// set the contract status to successful
if err := setContractStatus(u.tx, id, contracts.ContractStatusSuccessful); err != nil {
return fmt.Errorf("failed to set contract status to ended: %w", err)
}
return nil
}
Expand Down Expand Up @@ -436,18 +442,23 @@ func (s *Store) ContractFormationSet(id types.FileContractID) ([]types.Transacti
// if the contract is active or pending.
func (s *Store) ExpireContract(id types.FileContractID, status contracts.ContractStatus) error {
return s.transaction(func(tx txn) error {
if err := setContractStatus(tx, id, status); err != nil {
return fmt.Errorf("failed to set contract status: %w", err)
}

// reduce the locked and risked collateral metrics
contract, err := getContract(tx, id)
if err != nil {
return fmt.Errorf("failed to get contract: %w", err)
} else if err := incrementCurrencyStat(tx, metricLockedCollateral, contract.LockedCollateral, true, time.Now()); err != nil {
return fmt.Errorf("failed to increment locked collateral stat: %w", err)
} else if err := incrementCurrencyStat(tx, metricRiskedCollateral, contract.Usage.RiskedCollateral, true, time.Now()); err != nil {
return fmt.Errorf("failed to increment risked collateral stat: %w", err)
}
// successful, failed, and rejected contracts should have already had their
// collateral removed from the metrics
if contract.Status == contracts.ContractStatusActive || contract.Status == contracts.ContractStatusPending {
if err := incrementCurrencyStat(tx, metricLockedCollateral, contract.LockedCollateral, true, time.Now()); err != nil {
return fmt.Errorf("failed to increment locked collateral stat: %w", err)
} else if err := incrementCurrencyStat(tx, metricRiskedCollateral, contract.Usage.RiskedCollateral, true, time.Now()); err != nil {
return fmt.Errorf("failed to increment risked collateral stat: %w", err)
}
}
// update the contract status
if err := setContractStatus(tx, id, status); err != nil {
return fmt.Errorf("failed to set contract status: %w", err)
}
return nil
})
Expand Down Expand Up @@ -1014,13 +1025,13 @@ func scanContract(row scanner) (c contracts.Contract, err error) {
return
}

func updateContractMetrics(tx txn, prev, current contracts.ContractStatus) error {
if prev == current {
func updateContractMetrics(tx txn, current, next contracts.ContractStatus) error {
if current == next {
return nil
}

var initialMetric, finalMetric string
switch prev {
switch current {
case contracts.ContractStatusPending:
initialMetric = metricPendingContracts
case contracts.ContractStatusRejected:
Expand All @@ -1034,7 +1045,7 @@ func updateContractMetrics(tx txn, prev, current contracts.ContractStatus) error
default:
return fmt.Errorf("invalid prev contract status: %v", current)
}
switch current {
switch next {
case contracts.ContractStatusPending:
finalMetric = metricPendingContracts
case contracts.ContractStatusRejected:
Expand Down
2 changes: 1 addition & 1 deletion persist/sqlite/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -220,4 +220,4 @@ CREATE TABLE global_settings (
contracts_height INTEGER -- height of the contract manager as of the last processed change
);

INSERT INTO global_settings (id, db_version) VALUES (0, 10); -- version must be updated when the schema changes
INSERT INTO global_settings (id, db_version) VALUES (0, 11); -- version must be updated when the schema changes
27 changes: 27 additions & 0 deletions persist/sqlite/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,32 @@ import (
"go.sia.tech/hostd/host/contracts"
)

// migrateVersion11 recalculates the contract collateral metrics for existing contracts.
func migrateVersion11(tx txn) error {
rows, err := tx.Query(`SELECT locked_collateral, risked_collateral FROM contracts WHERE contract_status IN (?, ?)`, contracts.ContractStatusPending, contracts.ContractStatusActive)
if err != nil {
return fmt.Errorf("failed to query contracts: %w", err)
}
defer rows.Close()
var totalLocked, totalRisked types.Currency
for rows.Next() {
var locked, risked types.Currency
if err := rows.Scan((*sqlCurrency)(&locked), (*sqlCurrency)(&risked)); err != nil {
return fmt.Errorf("failed to scan contract: %w", err)
}
totalLocked = totalLocked.Add(locked)
totalRisked = totalRisked.Add(risked)
}

if err := setCurrencyStat(tx, metricLockedCollateral, totalLocked, time.Now()); err != nil {
return fmt.Errorf("failed to increment locked collateral: %w", err)
} else if err := setCurrencyStat(tx, metricRiskedCollateral, totalRisked, time.Now()); err != nil {
return fmt.Errorf("failed to increment risked collateral: %w", err)
}
return nil
}

// migrateVersion10 drops the log_lines table.
func migrateVersion10(tx txn) error {
_, err := tx.Exec(`DROP TABLE log_lines;`)
return err
Expand Down Expand Up @@ -238,4 +264,5 @@ var migrations = []func(tx txn) error{
migrateVersion8,
migrateVersion9,
migrateVersion10,
migrateVersion11,
}