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

refactor(contracts): support rhp4 #507

Merged
merged 5 commits into from
Nov 22, 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
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{}{}
n8maninger marked this conversation as resolved.
Show resolved Hide resolved
}
}

// 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
Loading