Skip to content

Commit

Permalink
rhp4 RPC Refresh (#104)
Browse files Browse the repository at this point in the history
* rhp4,chain: simplify renew, move proof updates to chain manager

* rhp4,chain: address comments

* rhp4: fix renew signatures

* rhp4: add explicit refresh RPC

* Update rhp/v4/rpc.go

Co-authored-by: Peter-Jan Brone <[email protected]>

* rhp4: deduplicate test setup

---------

Co-authored-by: Peter-Jan Brone <[email protected]>
  • Loading branch information
n8maninger and peterjan authored Oct 8, 2024
1 parent c7e1c25 commit 56f89a2
Show file tree
Hide file tree
Showing 5 changed files with 498 additions and 202 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.23.0

require (
go.etcd.io/bbolt v1.3.11
go.sia.tech/core v0.4.8-0.20241003192046-425f95763c90
go.sia.tech/core v0.4.8-0.20241008172640-77750c28f8db
go.sia.tech/mux v1.3.0
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.28.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,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.4.8-0.20241003192046-425f95763c90 h1:R/G7XXyzLKelfGBGT6XQpih379v5jJx6T4AsjLwyjJc=
go.sia.tech/core v0.4.8-0.20241003192046-425f95763c90/go.mod h1:j2Ke8ihV8or7d2VDrFZWcCkwSVHO0DNMQJAGs9Qop2M=
go.sia.tech/core v0.4.8-0.20241008172640-77750c28f8db h1:vsTfBHMyKCjpdSa4bARotyEYnxoOUZCpQYb7smc3QUM=
go.sia.tech/core v0.4.8-0.20241008172640-77750c28f8db/go.mod h1:j2Ke8ihV8or7d2VDrFZWcCkwSVHO0DNMQJAGs9Qop2M=
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
137 changes: 136 additions & 1 deletion rhp/v4/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ type (
RenewalSet TransactionSet `json:"renewalSet"`
Cost types.Currency `json:"cost"`
}

// RPCRefreshContractResult contains the result of executing the refresh contract RPC.
RPCRefreshContractResult struct {
Contract ContractRevision `json:"contract"`
RenewalSet TransactionSet `json:"renewalSet"`
Cost types.Currency `json:"cost"`
}
)

func callSingleRoundtripRPC(ctx context.Context, t TransportClient, rpcID types.Specifier, req, resp rhp4.Object) error {
Expand Down Expand Up @@ -630,7 +637,7 @@ func RPCFormContract(ctx context.Context, t TransportClient, tp TxPool, signer F

// RPCRenewContract renews a contract with a host.
func RPCRenewContract(ctx context.Context, t TransportClient, tp TxPool, signer FormContractSigner, cs consensus.State, p rhp4.HostPrices, existing types.V2FileContract, params rhp4.RPCRenewContractParams) (RPCRenewContractResult, error) {
renewal := rhp4.NewRenewal(existing, p, params)
renewal := rhp4.RenewContract(existing, p, params)
renewalTxn := types.V2Transaction{
MinerFee: tp.RecommendedFee().Mul64(1000),
FileContractResolutions: []types.V2FileContractResolution{
Expand Down Expand Up @@ -754,3 +761,131 @@ func RPCRenewContract(ctx context.Context, t TransportClient, tp TxPool, signer
Cost: renterCost,
}, nil
}

// RPCRefreshContract refreshes a contract with a host.
func RPCRefreshContract(ctx context.Context, t TransportClient, tp TxPool, signer FormContractSigner, cs consensus.State, p rhp4.HostPrices, existing types.V2FileContract, params rhp4.RPCRefreshContractParams) (RPCRefreshContractResult, error) {
renewal := rhp4.RefreshContract(existing, p, params)
renewalTxn := types.V2Transaction{
MinerFee: tp.RecommendedFee().Mul64(1000),
FileContractResolutions: []types.V2FileContractResolution{
{
Parent: types.V2FileContractElement{
StateElement: types.StateElement{
// the other parts of the state element are not required
// for signing the transaction. Let the host fill them
// in.
ID: types.Hash256(params.ContractID),
},
},
Resolution: &renewal,
},
},
}

renterCost, hostCost := rhp4.RefreshCost(cs, p, renewal, renewalTxn.MinerFee)
req := rhp4.RPCRefreshContractRequest{
Prices: p,
Refresh: params,
MinerFee: renewalTxn.MinerFee,
}

basis, toSign, err := signer.FundV2Transaction(&renewalTxn, renterCost)
if err != nil {
return RPCRefreshContractResult{}, fmt.Errorf("failed to fund transaction: %w", err)
}
signer.SignV2Inputs(&renewalTxn, toSign)

req.Basis, req.RenterParents, err = tp.V2TransactionSet(basis, renewalTxn)
if err != nil {
return RPCRefreshContractResult{}, fmt.Errorf("failed to get transaction set: %w", err)
}
for _, si := range renewalTxn.SiacoinInputs {
req.RenterInputs = append(req.RenterInputs, si.Parent)
}
req.RenterParents = req.RenterParents[:len(req.RenterParents)-1] // last transaction is the renewal

sigHash := req.ChallengeSigHash(existing.RevisionNumber)
req.ChallengeSignature = signer.SignHash(sigHash)

s := t.DialStream(ctx)
defer s.Close()

if err := rhp4.WriteRequest(s, rhp4.RPCRefreshContractID, &req); err != nil {
return RPCRefreshContractResult{}, fmt.Errorf("failed to write request: %w", err)
}

var hostInputsResp rhp4.RPCRefreshContractResponse
if err := rhp4.ReadResponse(s, &hostInputsResp); err != nil {
return RPCRefreshContractResult{}, fmt.Errorf("failed to read host inputs response: %w", err)
}

// add the host inputs to the transaction
var hostInputSum types.Currency
for _, si := range hostInputsResp.HostInputs {
hostInputSum = hostInputSum.Add(si.Parent.SiacoinOutput.Value)
renewalTxn.SiacoinInputs = append(renewalTxn.SiacoinInputs, si)
}

// verify the host added enough inputs
if n := hostInputSum.Cmp(hostCost); n < 0 {
return RPCRefreshContractResult{}, fmt.Errorf("expected host to fund %v, got %v", hostCost, hostInputSum)
} else if n > 0 {
// add change output
renewalTxn.SiacoinOutputs = append(renewalTxn.SiacoinOutputs, types.SiacoinOutput{
Address: existing.HostOutput.Address,
Value: hostInputSum.Sub(hostCost),
})
}

// sign the renter inputs
signer.SignV2Inputs(&renewalTxn, []int{0})
// sign the renewal
renewalSigHash := cs.RenewalSigHash(renewal)
renewal.RenterSignature = signer.SignHash(renewalSigHash)

// send the renter signatures
renterPolicyResp := rhp4.RPCRefreshContractSecondResponse{
RenterRenewalSignature: renewal.RenterSignature,
}
for _, si := range renewalTxn.SiacoinInputs[:len(req.RenterInputs)] {
renterPolicyResp.RenterSatisfiedPolicies = append(renterPolicyResp.RenterSatisfiedPolicies, si.SatisfiedPolicy)
}
if err := rhp4.WriteResponse(s, &renterPolicyResp); err != nil {
return RPCRefreshContractResult{}, fmt.Errorf("failed to write signature response: %w", err)
}

// read the finalized transaction set
var hostTransactionSetResp rhp4.RPCRefreshContractThirdResponse
if err := rhp4.ReadResponse(s, &hostTransactionSetResp); err != nil {
return RPCRefreshContractResult{}, fmt.Errorf("failed to read final response: %w", err)
}

if len(hostTransactionSetResp.TransactionSet) == 0 {
return RPCRefreshContractResult{}, fmt.Errorf("expected at least one host transaction")
}
hostRenewalTxn := hostTransactionSetResp.TransactionSet[len(hostTransactionSetResp.TransactionSet)-1]
if len(hostRenewalTxn.FileContractResolutions) != 1 {
return RPCRefreshContractResult{}, fmt.Errorf("expected exactly one resolution")
}

hostRenewal, ok := hostRenewalTxn.FileContractResolutions[0].Resolution.(*types.V2FileContractRenewal)
if !ok {
return RPCRefreshContractResult{}, fmt.Errorf("expected renewal resolution")
}

// validate the host signature
if !existing.HostPublicKey.VerifyHash(renewalSigHash, hostRenewal.HostSignature) {
return RPCRefreshContractResult{}, errors.New("invalid host signature")
}
return RPCRefreshContractResult{
Contract: ContractRevision{
ID: params.ContractID.V2RenewalID(),
Revision: renewal.NewContract,
},
RenewalSet: TransactionSet{
Basis: hostTransactionSetResp.Basis,
Transactions: hostTransactionSetResp.TransactionSet,
},
Cost: renterCost,
}, nil
}
Loading

0 comments on commit 56f89a2

Please sign in to comment.