Skip to content

Commit

Permalink
Merge pull request #724 from SiaFoundation/chris/prune-pending-contracts
Browse files Browse the repository at this point in the history
Add tracking of on-chain state for contract
  • Loading branch information
ChrisSchinnerl committed Nov 10, 2023
2 parents 20bdb2c + 83bbfe1 commit 058b1de
Show file tree
Hide file tree
Showing 12 changed files with 293 additions and 62 deletions.
2 changes: 2 additions & 0 deletions api/bus.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ type ConsensusNetwork struct {
type ContractsIDAddRequest struct {
Contract rhpv2.ContractRevision `json:"contract"`
StartHeight uint64 `json:"startHeight"`
State string `json:"state,omitempty"`
TotalCost types.Currency `json:"totalCost"`
}

Expand All @@ -122,6 +123,7 @@ type ContractsIDRenewedRequest struct {
Contract rhpv2.ContractRevision `json:"contract"`
RenewedFrom types.FileContractID `json:"renewedFrom"`
StartHeight uint64 `json:"startHeight"`
State string `json:"state,omitempty"`
TotalCost types.Currency `json:"totalCost"`
}

Expand Down
11 changes: 11 additions & 0 deletions api/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ import (
"go.sia.tech/core/types"
)

const (
ContractStateInvalid = "invalid"
ContractStateUnknown = "unknown"
ContractStatePending = "pending"
ContractStateActive = "active"
ContractStateComplete = "complete"
ContractStateFailed = "failed"
)

type (
// A Contract wraps the contract metadata with the latest contract revision.
Contract struct {
Expand All @@ -31,6 +40,7 @@ type (
RevisionNumber uint64 `json:"revisionNumber"`
Size uint64 `json:"size"`
StartHeight uint64 `json:"startHeight"`
State string `json:"state"`
WindowStart uint64 `json:"windowStart"`
WindowEnd uint64 `json:"windowEnd"`

Expand Down Expand Up @@ -68,6 +78,7 @@ type (
RevisionNumber uint64 `json:"revisionNumber"`
Size uint64 `json:"size"`
StartHeight uint64 `json:"startHeight"`
State string `json:"state"`
WindowStart uint64 `json:"windowStart"`
WindowEnd uint64 `json:"windowEnd"`
}
Expand Down
4 changes: 2 additions & 2 deletions autopilot/autopilot.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ type Bus interface {

// contracts
Contracts(ctx context.Context) (contracts []api.ContractMetadata, err error)
AddContract(ctx context.Context, c rhpv2.ContractRevision, totalCost types.Currency, startHeight uint64) (api.ContractMetadata, error)
AddRenewedContract(ctx context.Context, c rhpv2.ContractRevision, totalCost types.Currency, startHeight uint64, renewedFrom types.FileContractID) (api.ContractMetadata, error)
AddContract(ctx context.Context, c rhpv2.ContractRevision, totalCost types.Currency, startHeight uint64, state string) (api.ContractMetadata, error)
AddRenewedContract(ctx context.Context, c rhpv2.ContractRevision, totalCost types.Currency, startHeight uint64, renewedFrom types.FileContractID, state string) (api.ContractMetadata, error)
AncestorContracts(ctx context.Context, id types.FileContractID, minStartHeight uint64) ([]api.ArchivedContract, error)
ArchiveContracts(ctx context.Context, toArchive map[types.FileContractID]string) error
ContractSetContracts(ctx context.Context, set string) ([]api.ContractMetadata, error)
Expand Down
6 changes: 3 additions & 3 deletions autopilot/contractor.go
Original file line number Diff line number Diff line change
Expand Up @@ -1335,7 +1335,7 @@ func (c *contractor) renewContract(ctx context.Context, w Worker, ci contractInf
*budget = budget.Sub(renterFunds)

// persist the contract
renewedContract, err := c.ap.bus.AddRenewedContract(ctx, newRevision, renterFunds, cs.BlockHeight, fcid)
renewedContract, err := c.ap.bus.AddRenewedContract(ctx, newRevision, renterFunds, cs.BlockHeight, fcid, api.ContractStatePending)
if err != nil {
c.logger.Errorw(fmt.Sprintf("renewal failed to persist, err: %v", err), "hk", hk, "fcid", fcid)
return api.ContractMetadata{}, false, err
Expand Down Expand Up @@ -1423,7 +1423,7 @@ func (c *contractor) refreshContract(ctx context.Context, w Worker, ci contractI
*budget = budget.Sub(renterFunds)

// persist the contract
refreshedContract, err := c.ap.bus.AddRenewedContract(ctx, newRevision, renterFunds, cs.BlockHeight, contract.ID)
refreshedContract, err := c.ap.bus.AddRenewedContract(ctx, newRevision, renterFunds, cs.BlockHeight, contract.ID, api.ContractStatePending)
if err != nil {
c.logger.Errorw(fmt.Sprintf("refresh failed, err: %v", err), "hk", hk, "fcid", fcid)
return api.ContractMetadata{}, false, err
Expand Down Expand Up @@ -1495,7 +1495,7 @@ func (c *contractor) formContract(ctx context.Context, w Worker, host hostdb.Hos
*budget = budget.Sub(renterFunds)

// persist contract in store
formedContract, err := c.ap.bus.AddContract(ctx, contract, renterFunds, cs.BlockHeight)
formedContract, err := c.ap.bus.AddContract(ctx, contract, renterFunds, cs.BlockHeight, api.ContractStatePending)
if err != nil {
c.logger.Errorw(fmt.Sprintf("contract formation failed, err: %v", err), "hk", hk)
return api.ContractMetadata{}, true, err
Expand Down
14 changes: 10 additions & 4 deletions bus/bus.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ type (

// A MetadataStore stores information about contracts and objects.
MetadataStore interface {
AddContract(ctx context.Context, c rhpv2.ContractRevision, totalCost types.Currency, startHeight uint64) (api.ContractMetadata, error)
AddRenewedContract(ctx context.Context, c rhpv2.ContractRevision, totalCost types.Currency, startHeight uint64, renewedFrom types.FileContractID) (api.ContractMetadata, error)
AddContract(ctx context.Context, c rhpv2.ContractRevision, totalCost types.Currency, startHeight uint64, state string) (api.ContractMetadata, error)
AddRenewedContract(ctx context.Context, c rhpv2.ContractRevision, totalCost types.Currency, startHeight uint64, renewedFrom types.FileContractID, state string) (api.ContractMetadata, error)
AncestorContracts(ctx context.Context, fcid types.FileContractID, minStartHeight uint64) ([]api.ArchivedContract, error)
ArchiveContract(ctx context.Context, id types.FileContractID, reason string) error
ArchiveContracts(ctx context.Context, toArchive map[types.FileContractID]string) error
Expand Down Expand Up @@ -906,8 +906,11 @@ func (b *bus) contractIDHandlerPOST(jc jape.Context) {
http.Error(jc.ResponseWriter, "TotalCost can not be zero", http.StatusBadRequest)
return
}
if req.State == "" {
req.State = api.ContractStatePending
}

a, err := b.ms.AddContract(jc.Request.Context(), req.Contract, req.TotalCost, req.StartHeight)
a, err := b.ms.AddContract(jc.Request.Context(), req.Contract, req.TotalCost, req.StartHeight, req.State)
if jc.Check("couldn't store contract", err) == nil {
jc.Encode(a)
}
Expand All @@ -927,8 +930,11 @@ func (b *bus) contractIDRenewedHandlerPOST(jc jape.Context) {
http.Error(jc.ResponseWriter, "TotalCost can not be zero", http.StatusBadRequest)
return
}
if req.State == "" {
req.State = api.ContractStatePending
}

r, err := b.ms.AddRenewedContract(jc.Request.Context(), req.Contract, req.TotalCost, req.StartHeight, req.RenewedFrom)
r, err := b.ms.AddRenewedContract(jc.Request.Context(), req.Contract, req.TotalCost, req.StartHeight, req.RenewedFrom, req.State)
if jc.Check("couldn't store contract", err) == nil {
jc.Encode(r)
}
Expand Down
6 changes: 4 additions & 2 deletions bus/client/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,23 @@ import (
)

// AddContract adds the provided contract to the metadata store.
func (c *Client) AddContract(ctx context.Context, contract rhpv2.ContractRevision, totalCost types.Currency, startHeight uint64) (added api.ContractMetadata, err error) {
func (c *Client) AddContract(ctx context.Context, contract rhpv2.ContractRevision, totalCost types.Currency, startHeight uint64, state string) (added api.ContractMetadata, err error) {
err = c.c.WithContext(ctx).POST(fmt.Sprintf("/contract/%s", contract.ID()), api.ContractsIDAddRequest{
Contract: contract,
StartHeight: startHeight,
State: state,
TotalCost: totalCost,
}, &added)
return
}

// AddRenewedContract adds the provided contract to the metadata store.
func (c *Client) AddRenewedContract(ctx context.Context, contract rhpv2.ContractRevision, totalCost types.Currency, startHeight uint64, renewedFrom types.FileContractID) (renewed api.ContractMetadata, err error) {
func (c *Client) AddRenewedContract(ctx context.Context, contract rhpv2.ContractRevision, totalCost types.Currency, startHeight uint64, renewedFrom types.FileContractID, state string) (renewed api.ContractMetadata, err error) {
err = c.c.WithContext(ctx).POST(fmt.Sprintf("/contract/%s/renewed", contract.ID()), api.ContractsIDRenewedRequest{
Contract: contract,
RenewedFrom: renewedFrom,
StartHeight: startHeight,
State: state,
TotalCost: totalCost,
}, &renewed)
return
Expand Down
14 changes: 10 additions & 4 deletions internal/testing/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ func TestNewTestCluster(t *testing.T) {
if contracts[0].ProofHeight != 0 {
return errors.New("proof height should be 0 since the contract was renewed and therefore doesn't require a proof")
}
if contracts[0].State != api.ContractStatePending {
return fmt.Errorf("contract should be pending but was %v", contracts[0].State)
}
return nil
})

Expand All @@ -134,6 +137,7 @@ func TestNewTestCluster(t *testing.T) {
}

// Now wait for the revision and proof to be caught by the hostdb.
var ac api.ArchivedContract
tt.Retry(20, time.Second, func() error {
cluster.MineBlocks(1)

Expand All @@ -149,13 +153,16 @@ func TestNewTestCluster(t *testing.T) {
if len(archivedContracts) != 1 {
return fmt.Errorf("should have 1 archived contract but got %v", len(archivedContracts))
}
ac := archivedContracts[0]
ac = archivedContracts[0]
if ac.RevisionHeight == 0 || ac.RevisionNumber != math.MaxUint64 {
return fmt.Errorf("revision information is wrong: %v %v", ac.RevisionHeight, ac.RevisionNumber)
}
if ac.ProofHeight != 0 {
t.Fatal("proof height should be 0 since the contract was renewed and therefore doesn't require a proof")
}
if ac.State != api.ContractStateComplete {
return fmt.Errorf("contract should be complete but was %v", ac.State)
}
archivedContracts, err = cluster.Bus.AncestorContracts(context.Background(), contracts[0].ID, math.MaxUint32)
if err != nil {
t.Fatal(err)
Expand All @@ -165,7 +172,6 @@ func TestNewTestCluster(t *testing.T) {
}
return nil
})
tt.OK(err)

// Get host info for every host.
hosts, err := cluster.Bus.Hosts(context.Background(), api.GetHostsOptions{})
Expand Down Expand Up @@ -1246,11 +1252,11 @@ func TestUploadDownloadSameHost(t *testing.T) {
// form 2 more contracts with the same host
rev2, _, err := cluster.Worker.RHPForm(context.Background(), c.WindowStart, c.HostKey, c.HostIP, wallet.Address, c.RenterFunds(), c.Revision.ValidHostPayout())
tt.OK(err)
c2, err := cluster.Bus.AddContract(context.Background(), rev2, c.TotalCost, c.StartHeight)
c2, err := cluster.Bus.AddContract(context.Background(), rev2, c.TotalCost, c.StartHeight, api.ContractStatePending)
tt.OK(err)
rev3, _, err := cluster.Worker.RHPForm(context.Background(), c.WindowStart, c.HostKey, c.HostIP, wallet.Address, c.RenterFunds(), c.Revision.ValidHostPayout())
tt.OK(err)
c3, err := cluster.Bus.AddContract(context.Background(), rev3, c.TotalCost, c.StartHeight)
c3, err := cluster.Bus.AddContract(context.Background(), rev3, c.TotalCost, c.StartHeight, api.ContractStatePending)
tt.OK(err)

// create a contract set with all 3 contracts
Expand Down
34 changes: 15 additions & 19 deletions stores/hostdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -928,25 +928,6 @@ func (ss *SQLStore) processConsensusChangeHostDB(cc modules.ConsensusChange) {
ss.unappliedHostKeys[hostKey] = struct{}{}
})
}

// Update RevisionHeight and RevisionNumber for our contracts.
for _, txn := range sb.Transactions {
for _, rev := range txn.FileContractRevisions {
if ss.isKnownContract(types.FileContractID(rev.ParentID)) {
ss.unappliedRevisions[types.FileContractID(rev.ParentID)] = revisionUpdate{
height: height,
number: rev.NewRevisionNumber,
size: rev.NewFileSize,
}
}
}
// Get ProofHeight for our contracts.
for _, sp := range txn.StorageProofs {
if ss.isKnownContract(types.FileContractID(sp.ParentID)) {
ss.unappliedProofs[types.FileContractID(sp.ParentID)] = height
}
}
}
height++
}

Expand Down Expand Up @@ -1043,6 +1024,21 @@ func applyRevisionUpdate(db *gorm.DB, fcid types.FileContractID, rev revisionUpd
})
}

func updateContractState(db *gorm.DB, fcid types.FileContractID, cs contractState) error {
return updateActiveAndArchivedContract(db, fcid, map[string]interface{}{
"state": cs,
})
}

func markFailedContracts(db *gorm.DB, height uint64) error {
if err := db.Model(&dbContract{}).
Where("state = ? AND ? > window_end", contractStateActive, height).
Update("state", contractStateFailed).Error; err != nil {
return fmt.Errorf("failed to mark failed contracts: %w", err)
}
return nil
}

func updateProofHeight(db *gorm.DB, fcid types.FileContractID, blockHeight uint64) error {
return updateActiveAndArchivedContract(db, fcid, map[string]interface{}{
"proof_height": blockHeight,
Expand Down
Loading

0 comments on commit 058b1de

Please sign in to comment.