Skip to content

Commit

Permalink
Merge pull request #137 from SiaFoundation/nate/add-renewed
Browse files Browse the repository at this point in the history
Add revised and renewed fields to RPCLatestRevision
  • Loading branch information
n8maninger authored Dec 13, 2024
2 parents 85dd025 + 1a94262 commit ddc28ec
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
default: major
---

# Add revised and renewed fields to RPCLatestRevision

Adds two additional fields to the RPCLatestRevision response. The Revisable field indicates whether the host will accept further revisions to the contract. A host will not accept revisions too close to the proof window or revisions on contracts that have already been resolved. The Renewed field indicates whether the contract was renewed. If the contract was renewed, the renter can use FileContractID.V2RenewalID to get the ID of the new contract.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ toolchain go1.23.2

require (
go.etcd.io/bbolt v1.3.11
go.sia.tech/core v0.7.2
go.sia.tech/core v0.8.0
go.sia.tech/mux v1.3.0
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.31.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
go.sia.tech/core v0.7.2 h1:GAsZ77LE592VEBGNdKeXLV4old/zjLjH11RblHhYbP4=
go.sia.tech/core v0.7.2/go.mod h1:pRlqaLm8amh3b/OBTSqJMEXmhPT14RxjntlKPySRNpA=
go.sia.tech/core v0.8.0 h1:J6vZQlVhpj4bTVeuC2GKkfkGEs8jf0j651Kl1wwOxjg=
go.sia.tech/core v0.8.0/go.mod h1:Wj1qzvpMM2rqEQjwWJEbCBbe9VWX/mSJUu2Y2ABl1QA=
go.sia.tech/mux v1.3.0 h1:hgR34IEkqvfBKUJkAzGi31OADeW2y7D6Bmy/Jcbop9c=
go.sia.tech/mux v1.3.0/go.mod h1:I46++RD4beqA3cW9Xm9SwXbezwPqLvHhVs9HLpDtt58=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
Expand Down
8 changes: 0 additions & 8 deletions rhp/v4/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,3 @@ func WithPriceTableValidity(validity time.Duration) ServerOption {
s.priceTableValidity = validity
}
}

// WithContractProofWindowBuffer sets the buffer for revising a contract before
// its proof window starts.
func WithContractProofWindowBuffer(buffer uint64) ServerOption {
return func(s *Server) {
s.contractProofWindowBuffer = buffer
}
}
7 changes: 3 additions & 4 deletions rhp/v4/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -474,11 +474,10 @@ func RPCFundAccounts(ctx context.Context, t TransportClient, cs consensus.State,
}

// RPCLatestRevision returns the latest revision of a contract.
func RPCLatestRevision(ctx context.Context, t TransportClient, contractID types.FileContractID) (types.V2FileContract, error) {
func RPCLatestRevision(ctx context.Context, t TransportClient, contractID types.FileContractID) (resp rhp4.RPCLatestRevisionResponse, err error) {
req := rhp4.RPCLatestRevisionRequest{ContractID: contractID}
var resp rhp4.RPCLatestRevisionResponse
err := callSingleRoundtripRPC(ctx, t, rhp4.RPCLatestRevisionID, &req, &resp)
return resp.Contract, err
err = callSingleRoundtripRPC(ctx, t, rhp4.RPCLatestRevisionID, &req, &resp)
return
}

// RPCSectorRoots returns the sector roots for a contract.
Expand Down
53 changes: 50 additions & 3 deletions rhp/v4/rpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (fs *fundAndSign) Address() types.Address {
}

func testRenterHostPair(tb testing.TB, hostKey types.PrivateKey, cm rhp4.ChainManager, s rhp4.Syncer, w rhp4.Wallet, c rhp4.Contractor, sr rhp4.Settings, ss rhp4.Sectors, log *zap.Logger) rhp4.TransportClient {
rs := rhp4.NewServer(hostKey, cm, s, c, w, sr, ss, rhp4.WithContractProofWindowBuffer(10), rhp4.WithPriceTableValidity(2*time.Minute))
rs := rhp4.NewServer(hostKey, cm, s, c, w, sr, ss, rhp4.WithPriceTableValidity(2*time.Minute))
hostAddr := testutil.ServeSiaMux(tb, rs, log.Named("siamux"))

transport, err := rhp4.DialSiaMux(context.Background(), hostAddr, hostKey.PublicKey())
Expand Down Expand Up @@ -406,6 +406,15 @@ func TestRPCRefresh(t *testing.T) {
t.Fatal(err)
}
revision.Revision = aRes.Revision

rs, err := rhp4.RPCLatestRevision(context.Background(), transport, revision.ID)
if err != nil {
t.Fatal(err)
} else if rs.Renewed {
t.Fatal("expected contract to not be renewed")
} else if !rs.Revisable {
t.Fatal("expected contract to be revisable")
}
return revision
}

Expand Down Expand Up @@ -450,6 +459,15 @@ func TestRPCRefresh(t *testing.T) {
} else if !hostKey.PublicKey().VerifyHash(sigHash, refreshResult.Contract.Revision.HostSignature) {
t.Fatal("host signature verification failed")
}

rs, err := rhp4.RPCLatestRevision(context.Background(), transport, revision.ID)
if err != nil {
t.Fatal(err)
} else if !rs.Renewed {
t.Fatal("expected contract to be renewed")
} else if rs.Revisable {
t.Fatal("expected contract to not be revisable")
}
})
}

Expand Down Expand Up @@ -593,6 +611,15 @@ func TestRPCRenew(t *testing.T) {
} else if !hostKey.PublicKey().VerifyHash(sigHash, renewResult.Contract.Revision.HostSignature) {
t.Fatal("host signature verification failed")
}

rs, err := rhp4.RPCLatestRevision(context.Background(), transport, revision.ID)
if err != nil {
t.Fatal(err)
} else if !rs.Renewed {
t.Fatal("expected contract to be renewed")
} else if rs.Revisable {
t.Fatal("expected contract to not be revisable")
}
})

t.Run("full rollover", func(t *testing.T) {
Expand Down Expand Up @@ -622,6 +649,15 @@ func TestRPCRenew(t *testing.T) {
} else if !hostKey.PublicKey().VerifyHash(sigHash, renewResult.Contract.Revision.HostSignature) {
t.Fatal("host signature verification failed")
}

rs, err := rhp4.RPCLatestRevision(context.Background(), transport, revision.ID)
if err != nil {
t.Fatal(err)
} else if !rs.Renewed {
t.Fatal("expected contract to be renewed")
} else if rs.Revisable {
t.Fatal("expected contract to not be revisable")
}
})

t.Run("no rollover", func(t *testing.T) {
Expand Down Expand Up @@ -651,6 +687,15 @@ func TestRPCRenew(t *testing.T) {
} else if !hostKey.PublicKey().VerifyHash(sigHash, renewResult.Contract.Revision.HostSignature) {
t.Fatal("host signature verification failed")
}

rs, err := rhp4.RPCLatestRevision(context.Background(), transport, revision.ID)
if err != nil {
t.Fatal(err)
} else if !rs.Renewed {
t.Fatal("expected contract to be renewed")
} else if rs.Revisable {
t.Fatal("expected contract to not be revisable")
}
})
}

Expand Down Expand Up @@ -907,10 +952,12 @@ func TestAppendSectors(t *testing.T) {
assertLastRevision := func(t *testing.T) {
t.Helper()

lastRev, err := rhp4.RPCLatestRevision(context.Background(), transport, revision.ID)
rs, err := rhp4.RPCLatestRevision(context.Background(), transport, revision.ID)
if err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(lastRev, revision.Revision) {
}
lastRev := rs.Contract
if !reflect.DeepEqual(lastRev, revision.Revision) {
t.Log(lastRev)
t.Log(revision.Revision)
t.Fatalf("expected last revision to match")
Expand Down
33 changes: 16 additions & 17 deletions rhp/v4/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,10 @@ type (

// A RevisionState pairs a contract revision with its sector roots.
RevisionState struct {
Revision types.V2FileContract
Roots []types.Hash256
Revision types.V2FileContract
Renewed bool
Revisable bool
Roots []types.Hash256
}

// Contractor is an interface for managing a host's contracts.
Expand Down Expand Up @@ -123,9 +125,8 @@ type (

// A Server handles incoming RHP4 RPC.
Server struct {
hostKey types.PrivateKey
priceTableValidity time.Duration
contractProofWindowBuffer uint64
hostKey types.PrivateKey
priceTableValidity time.Duration

chain ChainManager
syncer Syncer
Expand All @@ -136,18 +137,15 @@ type (
}
)

func (s *Server) lockContractForRevision(contractID types.FileContractID) (rev RevisionState, unlock func(), _ error) {
rev, unlock, err := s.contractor.LockV2Contract(contractID)
func (s *Server) lockContractForRevision(contractID types.FileContractID) (RevisionState, func(), error) {
rs, unlock, err := s.contractor.LockV2Contract(contractID)
if err != nil {
return RevisionState{}, nil, fmt.Errorf("failed to lock contract: %w", err)
} else if rev.Revision.ProofHeight <= s.chain.Tip().Height+s.contractProofWindowBuffer {
} else if !rs.Revisable {
unlock()
return RevisionState{}, nil, errorBadRequest("contract too close to proof window")
} else if rev.Revision.RevisionNumber >= types.MaxRevisionNumber {
unlock()
return RevisionState{}, nil, errorBadRequest("contract is locked for revision")
return RevisionState{}, nil, errorBadRequest("contract is not revisable")
}
return rev, unlock, nil
return rs, unlock, nil
}

func (s *Server) handleRPCSettings(stream net.Conn) error {
Expand Down Expand Up @@ -451,7 +449,9 @@ func (s *Server) handleRPCLatestRevision(stream net.Conn) error {
unlock()

return rhp4.WriteResponse(stream, &rhp4.RPCLatestRevisionResponse{
Contract: state.Revision,
Contract: state.Revision,
Revisable: state.Revisable,
Renewed: state.Renewed,
})
}

Expand Down Expand Up @@ -1118,9 +1118,8 @@ func errorDecodingError(f string, p ...any) error {
// NewServer creates a new RHP4 server
func NewServer(pk types.PrivateKey, cm ChainManager, syncer Syncer, contracts Contractor, wallet Wallet, settings Settings, sectors Sectors, opts ...ServerOption) *Server {
s := &Server{
hostKey: pk,
priceTableValidity: 30 * time.Minute,
contractProofWindowBuffer: 10,
hostKey: pk,
priceTableValidity: 30 * time.Minute,

chain: cm,
syncer: syncer,
Expand Down
13 changes: 7 additions & 6 deletions testutil/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,14 @@ func (ec *EphemeralContractor) LockV2Contract(contractID types.FileContractID) (
return rhp4.RevisionState{}, nil, errors.New("contract not found")
}

_, renewed := ec.contracts[contractID.V2RenewalID()]

var once sync.Once
return rhp4.RevisionState{
Revision: rev,
Roots: ec.roots[contractID],
Revision: rev,
Revisable: !renewed && ec.tip.Height < rev.ProofHeight,
Renewed: renewed,
Roots: ec.roots[contractID],
}, func() {
once.Do(func() {
ec.mu.Lock()
Expand Down Expand Up @@ -159,8 +163,6 @@ func (ec *EphemeralContractor) RenewV2Contract(renewalSet rhp4.TransactionSet, _
existing, ok := ec.contracts[existingID]
if !ok {
return errors.New("contract not found")
} else if existing.RevisionNumber == types.MaxRevisionNumber {
return errors.New("contract already at max revision")
}

contractID := existingID.V2RenewalID()
Expand All @@ -175,7 +177,6 @@ func (ec *EphemeralContractor) RenewV2Contract(renewalSet rhp4.TransactionSet, _
return errors.New("invalid host signature")
}

delete(ec.contracts, existingID) // remove the existing contract
ec.contracts[contractID] = renewal.NewContract
ec.roots[contractID] = append([]types.Hash256(nil), ec.roots[existingID]...)
return nil
Expand Down Expand Up @@ -210,7 +211,7 @@ func (ec *EphemeralContractor) ReviseV2Contract(contractID types.FileContractID,
func (ec *EphemeralContractor) AccountBalance(account proto4.Account) (types.Currency, error) {
ec.mu.Lock()
defer ec.mu.Unlock()
balance, _ := ec.accounts[account]
balance := ec.accounts[account]
return balance, nil
}

Expand Down

0 comments on commit ddc28ec

Please sign in to comment.