From 573a25b9832dabca81fa7c483c07be0159ed4816 Mon Sep 17 00:00:00 2001 From: Chris Schinnerl Date: Mon, 5 Aug 2024 15:54:44 +0200 Subject: [PATCH 1/5] stores: update MarkPackedSlabsUploaded --- api/slab.go | 2 +- object/slab.go | 13 ++++--- stores/metadata.go | 71 ++++------------------------------- stores/sql/database.go | 5 +++ stores/sql/main.go | 78 +++++++++++++++++++++++++++++++++++++++ stores/sql/mysql/main.go | 4 ++ stores/sql/sqlite/main.go | 4 ++ 7 files changed, 106 insertions(+), 71 deletions(-) diff --git a/api/slab.go b/api/slab.go index 1a5d3fc79..65d19788d 100644 --- a/api/slab.go +++ b/api/slab.go @@ -74,6 +74,6 @@ type ( } ) -func (s UploadedPackedSlab) Contracts() map[types.PublicKey]map[types.FileContractID]struct{} { +func (s UploadedPackedSlab) Contracts() []types.FileContractID { return object.ContractsFromShards(s.Shards) } diff --git a/object/slab.go b/object/slab.go index 770df9ef6..e52e7bd7b 100644 --- a/object/slab.go +++ b/object/slab.go @@ -53,15 +53,16 @@ func NewPartialSlab(ec EncryptionKey, minShards uint8) Slab { // ContractsFromShards is a helper to extract all contracts used by a set of // shards. -func ContractsFromShards(shards []Sector) map[types.PublicKey]map[types.FileContractID]struct{} { - usedContracts := make(map[types.PublicKey]map[types.FileContractID]struct{}) +func ContractsFromShards(shards []Sector) []types.FileContractID { + var usedContracts []types.FileContractID + usedMap := make(map[types.FileContractID]struct{}) for _, shard := range shards { - for h, fcids := range shard.Contracts { + for _, fcids := range shard.Contracts { for _, fcid := range fcids { - if _, exists := usedContracts[h]; !exists { - usedContracts[h] = make(map[types.FileContractID]struct{}) + if _, exists := usedMap[fcid]; !exists { + usedContracts = append(usedContracts, fcid) } - usedContracts[h][fcid] = struct{}{} + usedMap[fcid] = struct{}{} } } } diff --git a/stores/metadata.go b/stores/metadata.go index 21dca732a..acb824e8a 100644 --- a/stores/metadata.go +++ b/stores/metadata.go @@ -1226,14 +1226,15 @@ func (s *SQLStore) MarkPackedSlabsUploaded(ctx context.Context, slabs []api.Uplo } } } - var fileName string - err := s.retryTransaction(ctx, func(tx *gorm.DB) error { - for _, slab := range slabs { - var err error - fileName, err = s.markPackedSlabUploaded(tx, slab) + var fileNames []string + err := s.db.Transaction(ctx, func(tx sql.DatabaseTx) error { + fileNames = make([]string, len(slabs)) + for i, slab := range slabs { + fileName, err := tx.MarkPackedSlabUploaded(ctx, slab) if err != nil { return err } + fileNames[i] = fileName } return nil }) @@ -1242,68 +1243,10 @@ func (s *SQLStore) MarkPackedSlabsUploaded(ctx context.Context, slabs []api.Uplo } // Delete buffer from disk. - s.slabBufferMgr.RemoveBuffers(fileName) + s.slabBufferMgr.RemoveBuffers(fileNames...) return nil } -func (s *SQLStore) markPackedSlabUploaded(tx *gorm.DB, slab api.UploadedPackedSlab) (string, error) { - // collect all used contracts - usedContracts := slab.Contracts() - contracts, err := fetchUsedContracts(tx, usedContracts) - if err != nil { - return "", err - } - - // find the slab - var sla dbSlab - if err := tx.Where("db_buffered_slab_id", slab.BufferID). - Take(&sla).Error; err != nil { - return "", err - } - - // update the slab - if err := tx.Model(&dbSlab{}). - Where("id", sla.ID). - Updates(map[string]interface{}{ - "db_buffered_slab_id": nil, - }).Error; err != nil { - return "", fmt.Errorf("failed to set buffered slab NULL: %w", err) - } - - // delete buffer - var fileName string - if err := tx.Raw("SELECT filename FROM buffered_slabs WHERE id = ?", slab.BufferID). - Scan(&fileName).Error; err != nil { - return "", err - } - if err := tx.Exec("DELETE FROM buffered_slabs WHERE id = ?", slab.BufferID).Error; err != nil { - return "", err - } - - // add the shards to the slab - var shards []dbSector - for i := range slab.Shards { - sector := dbSector{ - DBSlabID: sla.ID, - SlabIndex: i + 1, - LatestHost: publicKey(slab.Shards[i].LatestHost), - Root: slab.Shards[i].Root[:], - } - for _, fcids := range slab.Shards[i].Contracts { - for _, fcid := range fcids { - if c, ok := contracts[fcid]; ok { - sector.Contracts = append(sector.Contracts, c) - } - } - } - shards = append(shards, sector) - } - if err := tx.Create(&shards).Error; err != nil { - return "", fmt.Errorf("failed to create shards: %w", err) - } - return fileName, nil -} - func (s *SQLStore) pruneSlabsLoop() { for { select { diff --git a/stores/sql/database.go b/stores/sql/database.go index 5ab57c073..7e2b1aa4b 100644 --- a/stores/sql/database.go +++ b/stores/sql/database.go @@ -186,6 +186,11 @@ type ( // MakeDirsForPath creates all directories for a given object's path. MakeDirsForPath(ctx context.Context, path string) (int64, error) + // MarkPackedSlabUploaded marks the packed slab as uploaded in the + // database, causing the provided shards to be associated with the slab. + // The returned string contains the filename of the slab buffer on disk. + MarkPackedSlabUploaded(ctx context.Context, slab api.UploadedPackedSlab) (string, error) + // MultipartUpload returns the multipart upload with the given ID or // api.ErrMultipartUploadNotFound if the upload doesn't exist. MultipartUpload(ctx context.Context, uploadID string) (api.MultipartUpload, error) diff --git a/stores/sql/main.go b/stores/sql/main.go index 8b99b1f60..f37e30c59 100644 --- a/stores/sql/main.go +++ b/stores/sql/main.go @@ -2690,3 +2690,81 @@ func ObjectsBySlabKey(ctx context.Context, tx Tx, bucket string, slabKey object. } return objects, nil } + +func MarkPackedSlabUploaded(ctx context.Context, tx Tx, slab api.UploadedPackedSlab) (string, error) { + // fetch relevant slab info + var slabID, bufferedSlabID int64 + var bufferFileName string + if err := tx.QueryRow(ctx, ` + SELECT sla.id, bs.id, bs.filename + FROM slabs sla + INNER JOIN buffered_slabs bs ON buffered_slabs.id = sla.db_buffered_slab_id + WHERE sla.db_buffered_slab_id = ? + `, slab.BufferID). + Scan(&slabID, &bufferedSlabID, &bufferFileName); err != nil { + return "", fmt.Errorf("failed to fetch slab id: %w", err) + } + + // set 'db_buffered_slab_id' to NULL + if _, err := tx.Exec(ctx, "UPDATE slabs SET db_buffered_slab_id = NULL WHERE id = ?", slabID); err != nil { + return "", fmt.Errorf("failed to update slab: %w", err) + } + + // delete buffer slab + if _, err := tx.Exec(ctx, "DELETE FROM buffered_slabs WHERE id = ?", bufferedSlabID); err != nil { + return "", fmt.Errorf("failed to delete buffered slab: %w", err) + } + + // stmt to add sector + sectorStmt, err := tx.Prepare(ctx, "INSERT INTO sectors (db_slab_id, slab_index, latest_host, root) VALUES (?, ?, ?, ?)") + if err != nil { + return "", fmt.Errorf("failed to prepare statement to insert sectors: %w", err) + } + defer sectorStmt.Close() + + // stmt to get contrat id from fcid + contractIDStmt, err := tx.Prepare(ctx, "SELECT id FROM contracts WHERE contracts.fcid = ?") + if err != nil { + return "", fmt.Errorf("failed to prepare statement to fetch contract id: %w", err) + } + defer contractIDStmt.Close() + + // stmt to insert contract_sector + contractSectorStmt, err := tx.Prepare(ctx, "INSERT INTO contract_sectors (db_contract_id, db_sector_id) VALUES (?, ?)") + if err != nil { + return "", fmt.Errorf("failed to prepare statement to insert contract sectors: %w", err) + } + defer contractSectorStmt.Close() + + // insert shards + for i := range slab.Shards { + // insert shard + res, err := sectorStmt.Exec(ctx, slabID, i+1, PublicKey(slab.Shards[i].LatestHost), slab.Shards[i].Root[:]) + if err != nil { + return "", fmt.Errorf("failed to insert sector: %w", err) + } + sectorID, err := res.LastInsertId() + if err != nil { + return "", fmt.Errorf("failed to get sector id: %w", err) + } + + // insert contracts for shard + for _, fcids := range slab.Shards[i].Contracts { + for _, fcid := range fcids { + // fetch contract id + var contractID int64 + err := contractIDStmt.QueryRow(ctx, FileContractID(fcid)).Scan(&contractID) + if errors.Is(err, dsql.ErrNoRows) { + continue + } else if err != nil { + return "", fmt.Errorf("failed to fetch contract id: %w", err) + } + // insert contract sector + if _, err := contractSectorStmt.Exec(ctx, contractID, sectorID); err != nil { + return "", fmt.Errorf("failed to insert contract sector: %w", err) + } + } + } + } + return bufferFileName, nil +} diff --git a/stores/sql/mysql/main.go b/stores/sql/mysql/main.go index 6a883b8bf..928387e98 100644 --- a/stores/sql/mysql/main.go +++ b/stores/sql/mysql/main.go @@ -514,6 +514,10 @@ func (tx *MainDatabaseTx) MakeDirsForPath(ctx context.Context, path string) (int return dirID, nil } +func (tx *MainDatabaseTx) MarkPackedSlabUploaded(ctx context.Context, slab api.UploadedPackedSlab) (string, error) { + return ssql.MarkPackedSlabUploaded(ctx, tx, slab) +} + func (tx *MainDatabaseTx) MultipartUpload(ctx context.Context, uploadID string) (api.MultipartUpload, error) { return ssql.MultipartUpload(ctx, tx, uploadID) } diff --git a/stores/sql/sqlite/main.go b/stores/sql/sqlite/main.go index 32abf6460..c4de59e10 100644 --- a/stores/sql/sqlite/main.go +++ b/stores/sql/sqlite/main.go @@ -511,6 +511,10 @@ func (tx *MainDatabaseTx) MakeDirsForPath(ctx context.Context, path string) (int return dirID, nil } +func (tx *MainDatabaseTx) MarkPackedSlabUploaded(ctx context.Context, slab api.UploadedPackedSlab) (string, error) { + return ssql.MarkPackedSlabUploaded(ctx, tx, slab) +} + func (tx *MainDatabaseTx) MultipartUpload(ctx context.Context, uploadID string) (api.MultipartUpload, error) { return ssql.MultipartUpload(ctx, tx, uploadID) } From b4b1f4c483098bcb2fc1880bed5156c8fcee6491 Mon Sep 17 00:00:00 2001 From: Chris Schinnerl Date: Mon, 5 Aug 2024 16:02:29 +0200 Subject: [PATCH 2/5] stores: fix TestPartialSlab --- stores/sql/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stores/sql/main.go b/stores/sql/main.go index f37e30c59..664db61c3 100644 --- a/stores/sql/main.go +++ b/stores/sql/main.go @@ -2698,7 +2698,7 @@ func MarkPackedSlabUploaded(ctx context.Context, tx Tx, slab api.UploadedPackedS if err := tx.QueryRow(ctx, ` SELECT sla.id, bs.id, bs.filename FROM slabs sla - INNER JOIN buffered_slabs bs ON buffered_slabs.id = sla.db_buffered_slab_id + INNER JOIN buffered_slabs bs ON bs.id = sla.db_buffered_slab_id WHERE sla.db_buffered_slab_id = ? `, slab.BufferID). Scan(&slabID, &bufferedSlabID, &bufferFileName); err != nil { From e3fafaba65b0dbf43975029f720c8fc2954fe338 Mon Sep 17 00:00:00 2001 From: Chris Schinnerl Date: Mon, 5 Aug 2024 16:16:04 +0200 Subject: [PATCH 3/5] stores: fix TestMarkSlabUploadedAfterRenew+ --- stores/sql/main.go | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/stores/sql/main.go b/stores/sql/main.go index 664db61c3..53727a98d 100644 --- a/stores/sql/main.go +++ b/stores/sql/main.go @@ -2715,6 +2715,12 @@ func MarkPackedSlabUploaded(ctx context.Context, tx Tx, slab api.UploadedPackedS return "", fmt.Errorf("failed to delete buffered slab: %w", err) } + // fetch used contracts + usedContracts, err := FetchUsedContracts(ctx, tx, slab.Contracts()) + if err != nil { + return "", fmt.Errorf("failed to fetch used contracts: %w", err) + } + // stmt to add sector sectorStmt, err := tx.Prepare(ctx, "INSERT INTO sectors (db_slab_id, slab_index, latest_host, root) VALUES (?, ?, ?, ?)") if err != nil { @@ -2722,13 +2728,6 @@ func MarkPackedSlabUploaded(ctx context.Context, tx Tx, slab api.UploadedPackedS } defer sectorStmt.Close() - // stmt to get contrat id from fcid - contractIDStmt, err := tx.Prepare(ctx, "SELECT id FROM contracts WHERE contracts.fcid = ?") - if err != nil { - return "", fmt.Errorf("failed to prepare statement to fetch contract id: %w", err) - } - defer contractIDStmt.Close() - // stmt to insert contract_sector contractSectorStmt, err := tx.Prepare(ctx, "INSERT INTO contract_sectors (db_contract_id, db_sector_id) VALUES (?, ?)") if err != nil { @@ -2751,16 +2750,12 @@ func MarkPackedSlabUploaded(ctx context.Context, tx Tx, slab api.UploadedPackedS // insert contracts for shard for _, fcids := range slab.Shards[i].Contracts { for _, fcid := range fcids { - // fetch contract id - var contractID int64 - err := contractIDStmt.QueryRow(ctx, FileContractID(fcid)).Scan(&contractID) - if errors.Is(err, dsql.ErrNoRows) { + uc, ok := usedContracts[fcid] + if !ok { continue - } else if err != nil { - return "", fmt.Errorf("failed to fetch contract id: %w", err) } // insert contract sector - if _, err := contractSectorStmt.Exec(ctx, contractID, sectorID); err != nil { + if _, err := contractSectorStmt.Exec(ctx, uc.ID, sectorID); err != nil { return "", fmt.Errorf("failed to insert contract sector: %w", err) } } From cf98ae4b1720fac55e09da70b1f9f365d3ff6708 Mon Sep 17 00:00:00 2001 From: Chris Schinnerl Date: Mon, 5 Aug 2024 17:44:51 +0200 Subject: [PATCH 4/5] sql: update record contract spending --- stores/metadata.go | 46 +++++++++++++++------------------------ stores/sql/database.go | 3 +++ stores/sql/main.go | 39 +++++++++++++++++++++++++++++++++ stores/sql/mysql/main.go | 4 ++++ stores/sql/sqlite/main.go | 4 ++++ 5 files changed, 68 insertions(+), 28 deletions(-) diff --git a/stores/metadata.go b/stores/metadata.go index acb824e8a..eaeac7b4f 100644 --- a/stores/metadata.go +++ b/stores/metadata.go @@ -52,9 +52,7 @@ var ( pruneDirsAlertID = frand.Entropy256() ) -var ( - objectDeleteBatchSizes = []int64{10, 50, 100, 200, 500, 1000, 5000, 10000, 50000, 100000} -) +var objectDeleteBatchSizes = []int64{10, 50, 100, 200, 500, 1000, 5000, 10000, 50000, 100000} type ( contractState uint8 @@ -715,16 +713,11 @@ func (s *SQLStore) RecordContractSpending(ctx context.Context, records []api.Con } metrics := make([]api.ContractMetric, 0, len(squashedRecords)) for fcid, newSpending := range squashedRecords { - err := s.retryTransaction(ctx, func(tx *gorm.DB) error { - var contract dbContract - err := tx.Model(&dbContract{}). - Where("fcid", fileContractID(fcid)). - Joins("Host"). - Take(&contract).Error - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil // contract not found, continue with next one + err := s.db.Transaction(ctx, func(tx sql.DatabaseTx) error { + contract, err := tx.Contract(ctx, fcid) + if errors.Is(err, api.ErrContractNotFound) { } else if err != nil { - return err + return fmt.Errorf("failed to fetch contract: %w", err) } remainingCollateral := types.ZeroCurrency @@ -734,38 +727,35 @@ func (s *SQLStore) RecordContractSpending(ctx context.Context, records []api.Con m := api.ContractMetric{ Timestamp: api.TimeNow(), ContractID: fcid, - HostKey: types.PublicKey(contract.Host.PublicKey), + HostKey: contract.HostKey, RemainingCollateral: remainingCollateral, RemainingFunds: latestValues[fcid].validRenterPayout, RevisionNumber: latestValues[fcid].revision, - UploadSpending: types.Currency(contract.UploadSpending).Add(newSpending.Uploads), - DownloadSpending: types.Currency(contract.DownloadSpending).Add(newSpending.Downloads), - FundAccountSpending: types.Currency(contract.FundAccountSpending).Add(newSpending.FundAccount), - DeleteSpending: types.Currency(contract.DeleteSpending).Add(newSpending.Deletions), - ListSpending: types.Currency(contract.ListSpending).Add(newSpending.SectorRoots), + UploadSpending: contract.Spending.Uploads.Add(newSpending.Uploads), + DownloadSpending: contract.Spending.Downloads.Add(newSpending.Downloads), + FundAccountSpending: contract.Spending.FundAccount.Add(newSpending.FundAccount), + DeleteSpending: contract.Spending.Deletions.Add(newSpending.Deletions), + ListSpending: contract.Spending.SectorRoots.Add(newSpending.SectorRoots), } metrics = append(metrics, m) - updates := make(map[string]interface{}) + var updates api.ContractSpending if !newSpending.Uploads.IsZero() { - updates["upload_spending"] = currency(m.UploadSpending) + updates.Uploads = m.UploadSpending } if !newSpending.Downloads.IsZero() { - updates["download_spending"] = currency(m.DownloadSpending) + updates.Downloads = m.DownloadSpending } if !newSpending.FundAccount.IsZero() { - updates["fund_account_spending"] = currency(m.FundAccountSpending) + updates.FundAccount = m.FundAccountSpending } if !newSpending.Deletions.IsZero() { - updates["delete_spending"] = currency(m.DeleteSpending) + updates.Deletions = m.DeleteSpending } if !newSpending.SectorRoots.IsZero() { - updates["list_spending"] = currency(m.ListSpending) + updates.SectorRoots = m.ListSpending } - updates["revision_number"] = latestValues[fcid].revision - updates["size"] = latestValues[fcid].size - err = tx.Model(&contract).Updates(updates).Error - return err + return tx.RecordContractSpending(ctx, fcid, latestValues[fcid].revision, latestValues[fcid].size, updates) }) if err != nil { return err diff --git a/stores/sql/database.go b/stores/sql/database.go index 7e2b1aa4b..be9c28b2c 100644 --- a/stores/sql/database.go +++ b/stores/sql/database.go @@ -235,6 +235,9 @@ type ( // or slab buffer. PruneSlabs(ctx context.Context, limit int64) (int64, error) + // RecordContractSpending records new spending for a contract + RecordContractSpending(ctx context.Context, fcid types.FileContractID, revisionNumber, size uint64, newSpending api.ContractSpending) error + // RecordHostScans records the results of host scans in the database // such as recording the settings and price table of a host in case of // success and updating the uptime and downtime of a host. diff --git a/stores/sql/main.go b/stores/sql/main.go index 53727a98d..6d6f2d642 100644 --- a/stores/sql/main.go +++ b/stores/sql/main.go @@ -2763,3 +2763,42 @@ func MarkPackedSlabUploaded(ctx context.Context, tx Tx, slab api.UploadedPackedS } return bufferFileName, nil } + +func RecordContractSpending(ctx context.Context, tx Tx, fcid types.FileContractID, revisionNumber, size uint64, newSpending api.ContractSpending) error { + var updateKeys []string + var updateValues []interface{} + + if !newSpending.Uploads.IsZero() { + updateKeys = append(updateKeys, "upload_spending = ?") + updateValues = append(updateValues, Currency(newSpending.Uploads)) + } + if !newSpending.Downloads.IsZero() { + updateKeys = append(updateKeys, "download_spending = ?") + updateValues = append(updateValues, Currency(newSpending.Downloads)) + } + if !newSpending.FundAccount.IsZero() { + updateKeys = append(updateKeys, "fund_account_spending = ?") + updateValues = append(updateValues, Currency(newSpending.FundAccount)) + } + if !newSpending.Deletions.IsZero() { + updateKeys = append(updateKeys, "delete_spending = ?") + updateValues = append(updateValues, Currency(newSpending.Deletions)) + } + if !newSpending.SectorRoots.IsZero() { + updateKeys = append(updateKeys, "list_spending = ?") + updateValues = append(updateValues, Currency(newSpending.SectorRoots)) + } + updateKeys = append(updateKeys, "revision_number = ?", "size = ?") + updateValues = append(updateValues, revisionNumber, size) + + updateValues = append(updateValues, FileContractID(fcid)) + _, err := tx.Exec(ctx, fmt.Sprintf(` + UPDATE contracts + SET %s + WHERE fcid = ? + `, strings.Join(updateKeys, ",")), updateValues...) + if err != nil { + return fmt.Errorf("failed to record contract spending: %w", err) + } + return nil +} diff --git a/stores/sql/mysql/main.go b/stores/sql/mysql/main.go index 928387e98..d0ed17ea4 100644 --- a/stores/sql/mysql/main.go +++ b/stores/sql/mysql/main.go @@ -611,6 +611,10 @@ func (tx *MainDatabaseTx) PruneSlabs(ctx context.Context, limit int64) (int64, e return res.RowsAffected() } +func (tx *MainDatabaseTx) RecordContractSpending(ctx context.Context, fcid types.FileContractID, revisionNumber, size uint64, newSpending api.ContractSpending) error { + return ssql.RecordContractSpending(ctx, tx, fcid, revisionNumber, size, newSpending) +} + func (tx *MainDatabaseTx) RecordHostScans(ctx context.Context, scans []api.HostScan) error { return ssql.RecordHostScans(ctx, tx, scans) } diff --git a/stores/sql/sqlite/main.go b/stores/sql/sqlite/main.go index c4de59e10..920a59cba 100644 --- a/stores/sql/sqlite/main.go +++ b/stores/sql/sqlite/main.go @@ -608,6 +608,10 @@ func (tx *MainDatabaseTx) PruneSlabs(ctx context.Context, limit int64) (int64, e return res.RowsAffected() } +func (tx *MainDatabaseTx) RecordContractSpending(ctx context.Context, fcid types.FileContractID, revisionNumber, size uint64, newSpending api.ContractSpending) error { + return ssql.RecordContractSpending(ctx, tx, fcid, revisionNumber, size, newSpending) +} + func (tx *MainDatabaseTx) RecordHostScans(ctx context.Context, scans []api.HostScan) error { return ssql.RecordHostScans(ctx, tx, scans) } From 528d8f9fe2c883d3f587778af4ae2c426bea3597 Mon Sep 17 00:00:00 2001 From: Chris Schinnerl Date: Tue, 6 Aug 2024 18:42:45 +0200 Subject: [PATCH 5/5] stores: add size and revision number to TestRecordContractSpending --- stores/metadata_test.go | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/stores/metadata_test.go b/stores/metadata_test.go index 170f4990d..41446ff71 100644 --- a/stores/metadata_test.go +++ b/stores/metadata_test.go @@ -2328,9 +2328,10 @@ func TestRecordContractSpending(t *testing.T) { cm, err := ss.addTestContract(fcid, hk) if err != nil { t.Fatal(err) - } - if cm.Spending != (api.ContractSpending{}) { + } else if cm.Spending != (api.ContractSpending{}) { t.Fatal("spending should be all 0") + } else if cm.Size != 0 && cm.RevisionNumber != 0 { + t.Fatalf("unexpected size or revision number, %v %v", cm.Size, cm.RevisionNumber) } // Record some spending. @@ -2350,6 +2351,8 @@ func TestRecordContractSpending(t *testing.T) { { ContractID: fcid, ContractSpending: expectedSpending, + RevisionNumber: 100, + Size: 200, }, }) if err != nil { @@ -2358,16 +2361,20 @@ func TestRecordContractSpending(t *testing.T) { cm2, err := ss.Contract(context.Background(), fcid) if err != nil { t.Fatal(err) - } - if cm2.Spending != expectedSpending { + } else if cm2.Spending != expectedSpending { t.Fatal("invalid spending", cm2.Spending, expectedSpending) + } else if cm2.Size != 200 && cm2.RevisionNumber != 100 { + t.Fatalf("unexpected size or revision number, %v %v", cm2.Size, cm2.RevisionNumber) } - // Record the same spending again. + // Record the same spending again but with a lower revision number. This + // shouldn't update the size. err = ss.RecordContractSpending(context.Background(), []api.ContractSpendingRecord{ { ContractID: fcid, ContractSpending: expectedSpending, + RevisionNumber: 100, + Size: 200, }, }) if err != nil { @@ -2377,9 +2384,10 @@ func TestRecordContractSpending(t *testing.T) { cm3, err := ss.Contract(context.Background(), fcid) if err != nil { t.Fatal(err) - } - if cm3.Spending != expectedSpending { + } else if cm3.Spending != expectedSpending { t.Fatal("invalid spending") + } else if cm2.Size != 200 && cm2.RevisionNumber != 100 { + t.Fatalf("unexpected size or revision number, %v %v", cm2.Size, cm2.RevisionNumber) } }