Skip to content

Commit

Permalink
Merge pull request #507 from SiaFoundation/nate/refactor-contract-man…
Browse files Browse the repository at this point in the history
…ager

refactor(contracts): support rhp4
  • Loading branch information
n8maninger authored Nov 22, 2024
2 parents d978a8f + 16de117 commit de44551
Show file tree
Hide file tree
Showing 13 changed files with 903 additions and 401 deletions.
46 changes: 2 additions & 44 deletions host/contracts/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

rhp2 "go.sia.tech/core/rhp/v2"
proto4 "go.sia.tech/core/rhp/v4"
"go.sia.tech/core/types"
"go.uber.org/zap"
)
Expand Down Expand Up @@ -54,8 +55,6 @@ const (
// V2ContractStatusActive indicates that the contract has been confirmed on
// the blockchain and is currently active.
V2ContractStatusActive V2ContractStatus = "active"
// V2ContractStatusFinalized indicates that the contract has been finalized.
V2ContractStatusFinalized V2ContractStatus = "finalized"
// V2ContractStatusRenewed indicates that the contract has been renewed.
V2ContractStatusRenewed V2ContractStatus = "renewed"
// V2ContractStatusSuccessful indicates that a storage proof has been
Expand Down Expand Up @@ -106,23 +105,13 @@ type (
RiskedCollateral types.Currency `json:"riskedCollateral"`
}

// V2Usage tracks the usage of a contract's funds.
V2Usage struct {
RPCRevenue types.Currency `json:"rpc"`
StorageRevenue types.Currency `json:"storage"`
EgressRevenue types.Currency `json:"egress"`
IngressRevenue types.Currency `json:"ingress"`
AccountFunding types.Currency `json:"accountFunding"`
RiskedCollateral types.Currency `json:"riskedCollateral"`
}

// A V2Contract contains metadata on the current state of a v2 file contract.
V2Contract struct {
types.V2FileContract

ID types.FileContractID `json:"id"`
Status V2ContractStatus `json:"status"`
Usage V2Usage `json:"usage"`
Usage proto4.Usage `json:"usage"`

// NegotiationHeight is the height the contract was negotiated at.
NegotiationHeight uint64 `json:"negotiationHeight"`
Expand All @@ -144,13 +133,6 @@ type (
RenewedFrom types.FileContractID `json:"renewedFrom"`
}

// A V2FormationTransactionSet contains the formation transaction set for a
// v2 contract.
V2FormationTransactionSet struct {
TransactionSet []types.V2Transaction
Basis types.ChainIndex
}

// A Contract contains metadata on the current state of a file contract.
Contract struct {
SignedRevision
Expand Down Expand Up @@ -288,30 +270,6 @@ func (a Usage) Sub(b Usage) (c Usage) {
}
}

// Add returns u + b
func (a V2Usage) Add(b V2Usage) (c V2Usage) {
return V2Usage{
RPCRevenue: a.RPCRevenue.Add(b.RPCRevenue),
StorageRevenue: a.StorageRevenue.Add(b.StorageRevenue),
EgressRevenue: a.EgressRevenue.Add(b.EgressRevenue),
IngressRevenue: a.IngressRevenue.Add(b.IngressRevenue),
AccountFunding: a.AccountFunding.Add(b.AccountFunding),
RiskedCollateral: a.RiskedCollateral.Add(b.RiskedCollateral),
}
}

// Sub returns a - b
func (a V2Usage) Sub(b V2Usage) (c V2Usage) {
return V2Usage{
RPCRevenue: a.RPCRevenue.Sub(b.RPCRevenue),
StorageRevenue: a.StorageRevenue.Sub(b.StorageRevenue),
EgressRevenue: a.EgressRevenue.Sub(b.EgressRevenue),
IngressRevenue: a.IngressRevenue.Sub(b.IngressRevenue),
AccountFunding: a.AccountFunding.Sub(b.AccountFunding),
RiskedCollateral: a.RiskedCollateral.Sub(b.RiskedCollateral),
}
}

// String returns the string representation of a ContractStatus.
func (c ContractStatus) String() string {
switch c {
Expand Down
138 changes: 138 additions & 0 deletions host/contracts/lock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package contracts

import (
"context"
"fmt"
"sync"

"go.sia.tech/core/types"
rhp4 "go.sia.tech/coreutils/rhp/v4"
)

type (
lock struct {
ch chan struct{}
n int
}

locker struct {
mu sync.Mutex
locks map[types.FileContractID]*lock
}
)

func newLocker() *locker {
l := &locker{
locks: make(map[types.FileContractID]*lock),
}
return l
}

// Unlock releases a lock on the given contract ID. If the lock is not held, the
// function will panic.
func (lr *locker) Unlock(id types.FileContractID) {
lr.mu.Lock()
defer lr.mu.Unlock()
l, ok := lr.locks[id]
if !ok {
panic("unlocking unheld lock") // developer error
}
l.n--
if l.n == 0 {
delete(lr.locks, id)
} else {
l.ch <- struct{}{}
}
}

// Lock acquires a lock on the given contract ID. If the lock is already held, the
// function will block until the lock is released or the context is canceled.
// If the context is canceled, the function will return an error.
func (lr *locker) Lock(ctx context.Context, id types.FileContractID) error {
lr.mu.Lock()
l, ok := lr.locks[id]
if !ok {
// immediately acquire the lock
defer lr.mu.Unlock()
l = &lock{
ch: make(chan struct{}, 1),
n: 1,
}
lr.locks[id] = l
return nil
}
l.n++
lr.mu.Unlock() // unlock before waiting to avoid deadlock
select {
case <-ctx.Done():
lr.mu.Lock()
l.n--
if l.n == 0 {
delete(lr.locks, id)
}
lr.mu.Unlock()
return ctx.Err()
case <-l.ch:
return nil
}
}

// Lock locks a contract for modification.
//
// Deprecated: Use LockV2Contract instead.
func (cm *Manager) Lock(ctx context.Context, id types.FileContractID) (SignedRevision, error) {
ctx, cancel, err := cm.tg.AddContext(ctx)
if err != nil {
return SignedRevision{}, err
}
defer cancel()

if err := cm.locks.Lock(ctx, id); err != nil {
return SignedRevision{}, err
}

contract, err := cm.store.Contract(id)
if err != nil {
cm.locks.Unlock(id)
return SignedRevision{}, fmt.Errorf("failed to get contract: %w", err)
} else if err := cm.isGoodForModification(contract); err != nil {
cm.locks.Unlock(id)
return SignedRevision{}, fmt.Errorf("contract is not good for modification: %w", err)
}
return contract.SignedRevision, nil
}

// Unlock unlocks a locked contract.
//
// Deprecated: Use LockV2Contract instead.
func (cm *Manager) Unlock(id types.FileContractID) {
cm.locks.Unlock(id)
}

// LockV2Contract locks a contract for modification. The returned unlock function
// must be called to release the lock.
func (cm *Manager) LockV2Contract(id types.FileContractID) (rev rhp4.RevisionState, unlock func(), _ error) {
done, err := cm.tg.Add()
if err != nil {
return rhp4.RevisionState{}, nil, err
}
defer done()

// blocking is fine because locks are held for a short time
if err := cm.locks.Lock(context.Background(), id); err != nil {
return rhp4.RevisionState{}, nil, err
}

contract, err := cm.store.V2Contract(id)
if err != nil {
cm.locks.Unlock(id)
return rhp4.RevisionState{}, nil, fmt.Errorf("failed to get contract: %w", err)
}

return rhp4.RevisionState{
Revision: contract.V2FileContract,
Roots: cm.getSectorRoots(id),
}, func() {
cm.locks.Unlock(id)
}, nil
}
Loading

0 comments on commit de44551

Please sign in to comment.