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

Add revised and renewed fields to RPCLatestRevision #137

Merged
merged 2 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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.7.4-0.20241212191304-8383d656cbed
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.7.4-0.20241212191304-8383d656cbed h1:L3HgQFyW34ZBlhtUjPzdzeBCLt+v2CAmDy/6BN1PXl0=
go.sia.tech/core v0.7.4-0.20241212191304-8383d656cbed/go.mod h1:nKaUneURNBl2ATp3dWOd1VS7Oe6HiXzUGMIBA83uUGM=
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
Loading