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

Ensure fetchUsedContracts can deal with renewed contracts properly #1168

Merged
merged 1 commit into from
Apr 16, 2024
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
27 changes: 16 additions & 11 deletions stores/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -1469,13 +1469,16 @@ func (s *SQLStore) isKnownContract(fcid types.FileContractID) bool {
return found
}

func fetchUsedContracts(tx *gorm.DB, usedContracts map[types.PublicKey]map[types.FileContractID]struct{}) (map[types.FileContractID]dbContract, error) {
fcids := make([]fileContractID, 0, len(usedContracts))
for _, hostFCIDs := range usedContracts {
func fetchUsedContracts(tx *gorm.DB, usedContractsByHost map[types.PublicKey]map[types.FileContractID]struct{}) (map[types.FileContractID]dbContract, error) {
// flatten map to get all used contract ids
fcids := make([]fileContractID, 0, len(usedContractsByHost))
for _, hostFCIDs := range usedContractsByHost {
for fcid := range hostFCIDs {
fcids = append(fcids, fileContractID(fcid))
}
}

// fetch all contracts, take into account renewals
var contracts []dbContract
err := tx.Model(&dbContract{}).
Joins("Host").
Expand All @@ -1484,17 +1487,19 @@ func fetchUsedContracts(tx *gorm.DB, usedContracts map[types.PublicKey]map[types
if err != nil {
return nil, err
}
fetchedContracts := make(map[types.FileContractID]dbContract, len(contracts))

// build map of used contracts
usedContracts := make(map[types.FileContractID]dbContract, len(contracts))
for _, c := range contracts {
// If a contract has been renewed, we add the renewed contract to the
// map using the old contract's id.
if _, renewed := usedContracts[types.PublicKey(c.Host.PublicKey)][types.FileContractID(c.RenewedFrom)]; renewed {
fetchedContracts[types.FileContractID(c.RenewedFrom)] = c
} else {
fetchedContracts[types.FileContractID(c.FCID)] = c
if _, used := usedContractsByHost[types.PublicKey(c.Host.PublicKey)][types.FileContractID(c.FCID)]; used {
usedContracts[types.FileContractID(c.FCID)] = c
}
if _, used := usedContractsByHost[types.PublicKey(c.Host.PublicKey)][types.FileContractID(c.RenewedFrom)]; used {
usedContracts[types.FileContractID(c.RenewedFrom)] = c
}
}
return fetchedContracts, nil

return usedContracts, nil
}

func (s *SQLStore) RenameObject(ctx context.Context, bucket, keyOld, keyNew string, force bool) error {
Expand Down
80 changes: 80 additions & 0 deletions stores/metadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4664,3 +4664,83 @@ func TestUpdateObjectParallel(t *testing.T) {
close(c)
wg.Wait()
}

// TestFetchUsedContracts is a unit test that verifies the functionality of
// fetchUsedContracts
func TestFetchUsedContracts(t *testing.T) {
// create store
ss := newTestSQLStore(t, defaultTestSQLStoreConfig)
defer ss.Close()

// add test host
hk1 := types.PublicKey{1}
err := ss.addTestHost(hk1)
if err != nil {
t.Fatal(err)
}

// add test contract
fcid1 := types.FileContractID{1}
_, err = ss.addTestContract(fcid1, hk1)
if err != nil {
t.Fatal(err)
}

// assert empty map returns no contracts
usedContracts := make(map[types.PublicKey]map[types.FileContractID]struct{})
contracts, err := fetchUsedContracts(ss.db, usedContracts)
if err != nil {
t.Fatal(err)
} else if len(contracts) != 0 {
t.Fatal("expected 0 contracts", len(contracts))
}

// add an entry for fcid1
usedContracts[hk1] = make(map[types.FileContractID]struct{})
usedContracts[hk1][types.FileContractID{1}] = struct{}{}

// assert we get the used contract
contracts, err = fetchUsedContracts(ss.db, usedContracts)
if err != nil {
t.Fatal(err)
} else if len(contracts) != 1 {
t.Fatal("expected 1 contract", len(contracts))
} else if _, ok := contracts[fcid1]; !ok {
t.Fatal("contract not found")
}

// renew the contract
fcid2 := types.FileContractID{2}
_, err = ss.addTestRenewedContract(fcid2, fcid1, hk1, 1)
if err != nil {
t.Fatal(err)
}

// assert used contracts contains one entry and it points to the renewal
contracts, err = fetchUsedContracts(ss.db, usedContracts)
if err != nil {
t.Fatal(err)
} else if len(contracts) != 1 {
t.Fatal("expected 1 contract", len(contracts))
} else if contract, ok := contracts[fcid1]; !ok {
t.Fatal("contract not found")
} else if contract.convert().ID != fcid2 {
t.Fatal("contract should point to the renewed contract")
}

// add an entry for fcid2
usedContracts[hk1][types.FileContractID{2}] = struct{}{}

// assert used contracts now contains an entry for both contracts and both
// point to the renewed contract
contracts, err = fetchUsedContracts(ss.db, usedContracts)
if err != nil {
t.Fatal(err)
} else if len(contracts) != 2 {
t.Fatal("expected 2 contracts", len(contracts))
} else if !reflect.DeepEqual(contracts[types.FileContractID{1}], contracts[types.FileContractID{2}]) {
t.Fatal("contracts should match")
} else if contracts[types.FileContractID{1}].convert().ID != fcid2 {
t.Fatal("contracts should point to the renewed contract")
}
}
Loading