diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..94f1119 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +.vscode diff --git a/go.mod b/go.mod index a1ff3a1..82bfc26 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.23.2 require ( go.etcd.io/bbolt v1.3.11 - go.sia.tech/core v0.6.2 + go.sia.tech/core v0.7.1-0.20241203043244-c435a355b1da go.sia.tech/mux v1.3.0 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.29.0 diff --git a/go.sum b/go.sum index 8bcd91f..085e6d6 100644 --- a/go.sum +++ b/go.sum @@ -6,10 +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.6.1 h1:eaExM2E2eNr43su2XDkY5J24E3F54YGS7hcC3WtVjVk= -go.sia.tech/core v0.6.1/go.mod h1:P3C1BWa/7J4XgdzWuaYHBvLo2RzZ0UBaJM4TG1GWB2g= -go.sia.tech/core v0.6.2 h1:8NEjxyD93A+EhZopsBy/LvuHH+zUSjRNKnf9rXgtIwU= -go.sia.tech/core v0.6.2/go.mod h1:4v+aT/33857tMfqa5j5OYlAoLsoIrd4d7qMlgeP+VGk= +go.sia.tech/core v0.7.1-0.20241203043244-c435a355b1da h1:taO86czGly5SIb8UswVI2W7rmxhmv9G4C93zoAwtfxk= +go.sia.tech/core v0.7.1-0.20241203043244-c435a355b1da/go.mod h1:4v+aT/33857tMfqa5j5OYlAoLsoIrd4d7qMlgeP+VGk= 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= diff --git a/miner.go b/miner.go index 85ad705..db4358a 100644 --- a/miner.go +++ b/miner.go @@ -1,7 +1,6 @@ package coreutils import ( - "encoding/binary" "time" "go.sia.tech/core/consensus" @@ -12,22 +11,10 @@ import ( // FindBlockNonce attempts to find a nonce for b that meets the PoW target. func FindBlockNonce(cs consensus.State, b *types.Block, timeout time.Duration) bool { b.Nonce = 0 - buf := make([]byte, 32+8+8+32) - binary.LittleEndian.PutUint64(buf[32:], b.Nonce) - binary.LittleEndian.PutUint64(buf[40:], uint64(b.Timestamp.Unix())) - if b.V2 != nil { - copy(buf[:32], "sia/id/block|") - copy(buf[48:], b.V2.Commitment[:]) - } else { - root := b.MerkleRoot() - copy(buf[:32], b.ParentID[:]) - copy(buf[48:], root[:]) - } factor := cs.NonceFactor() startBlock := time.Now() - for types.BlockID(types.HashBytes(buf)).CmpWork(cs.ChildTarget) < 0 { + for b.ID().CmpWork(cs.ChildTarget) < 0 { b.Nonce += factor - binary.LittleEndian.PutUint64(buf[32:], b.Nonce) if time.Since(startBlock) > timeout { return false } diff --git a/rhp/v4/rpc.go b/rhp/v4/rpc.go index 93d5973..34a8fd7 100644 --- a/rhp/v4/rpc.go +++ b/rhp/v4/rpc.go @@ -246,11 +246,10 @@ func RPCWriteSector(ctx context.Context, t TransportClient, prices rhp4.HostPric req := rhp4.RPCWriteSectorRequest{ Prices: prices, Token: token, - Duration: duration, DataLength: length, } - if err := req.Validate(t.PeerKey(), req.Duration); err != nil { + if err := req.Validate(t.PeerKey()); err != nil { return RPCWriteSectorResult{}, fmt.Errorf("invalid request: %w", err) } @@ -287,7 +286,7 @@ func RPCWriteSector(ctx context.Context, t TransportClient, prices rhp4.HostPric return RPCWriteSectorResult{ Root: resp.Root, - Usage: prices.RPCWriteSectorCost(uint64(length), duration), + Usage: prices.RPCWriteSectorCost(uint64(length)), }, nil } @@ -419,6 +418,7 @@ func RPCAppendSectors(ctx context.Context, t TransportClient, cs consensus.State } else if !contract.Revision.HostPublicKey.VerifyHash(sigHash, hostSignature.HostSignature) { return RPCAppendSectorsResult{}, rhp4.ErrInvalidSignature } + revision.HostSignature = hostSignature.HostSignature return RPCAppendSectorsResult{ Revision: revision, Usage: usage, @@ -498,7 +498,7 @@ func RPCSectorRoots(ctx context.Context, t TransportClient, cs consensus.State, RenterSignature: revision.RenterSignature, } - if err := req.Validate(contract.Revision.HostPublicKey, revision, length); err != nil { + if err := req.Validate(contract.Revision.HostPublicKey, revision); err != nil { return RPCSectorRootsResult{}, fmt.Errorf("invalid request: %w", err) } @@ -597,10 +597,10 @@ func RPCFormContract(ctx context.Context, t TransportClient, tp TxPool, signer F // sign the renter inputs after the host inputs have been added signer.SignV2Inputs(&formationTxn, toSign) formationSigHash := cs.ContractSigHash(fc) - formationTxn.FileContracts[0].RenterSignature = signer.SignHash(formationSigHash) + fc.RenterSignature = signer.SignHash(formationSigHash) renterPolicyResp := rhp4.RPCFormContractSecondResponse{ - RenterContractSignature: formationTxn.FileContracts[0].RenterSignature, + RenterContractSignature: fc.RenterSignature, } for _, si := range formationTxn.SiacoinInputs[:len(renterSiacoinElements)] { renterPolicyResp.RenterSatisfiedPolicies = append(renterPolicyResp.RenterSatisfiedPolicies, si.SatisfiedPolicy) @@ -727,10 +727,14 @@ func RPCRenewContract(ctx context.Context, t TransportClient, tp TxPool, signer // sign the renewal renewalSigHash := cs.RenewalSigHash(renewal) renewal.RenterSignature = signer.SignHash(renewalSigHash) + // sign the contract + contractSigHash := cs.ContractSigHash(renewal.NewContract) + renewal.NewContract.RenterSignature = signer.SignHash(contractSigHash) // send the renter signatures renterPolicyResp := rhp4.RPCRenewContractSecondResponse{ - RenterRenewalSignature: renewal.RenterSignature, + RenterRenewalSignature: renewal.RenterSignature, + RenterContractSignature: renewal.NewContract.RenterSignature, } for _, si := range renewalTxn.SiacoinInputs[:len(req.RenterInputs)] { renterPolicyResp.RenterSatisfiedPolicies = append(renterPolicyResp.RenterSatisfiedPolicies, si.SatisfiedPolicy) @@ -760,12 +764,14 @@ func RPCRenewContract(ctx context.Context, t TransportClient, tp TxPool, signer // validate the host signature if !existing.HostPublicKey.VerifyHash(renewalSigHash, hostRenewal.HostSignature) { - return RPCRenewContractResult{}, errors.New("invalid host signature") + return RPCRenewContractResult{}, errors.New("invalid host renewal signature") + } else if !existing.HostPublicKey.VerifyHash(contractSigHash, hostRenewal.NewContract.HostSignature) { + return RPCRenewContractResult{}, errors.New("invalid host contract signature") } return RPCRenewContractResult{ Contract: ContractRevision{ ID: params.ContractID.V2RenewalID(), - Revision: renewal.NewContract, + Revision: hostRenewal.NewContract, }, RenewalSet: TransactionSet{ Basis: hostTransactionSetResp.Basis, @@ -852,10 +858,14 @@ func RPCRefreshContract(ctx context.Context, t TransportClient, tp TxPool, signe // sign the renewal renewalSigHash := cs.RenewalSigHash(renewal) renewal.RenterSignature = signer.SignHash(renewalSigHash) + // sign the new contract + contractSigHash := cs.ContractSigHash(renewal.NewContract) + renewal.NewContract.RenterSignature = signer.SignHash(contractSigHash) // send the renter signatures renterPolicyResp := rhp4.RPCRefreshContractSecondResponse{ - RenterRenewalSignature: renewal.RenterSignature, + RenterRenewalSignature: renewal.RenterSignature, + RenterContractSignature: renewal.NewContract.RenterSignature, } for _, si := range renewalTxn.SiacoinInputs[:len(req.RenterInputs)] { renterPolicyResp.RenterSatisfiedPolicies = append(renterPolicyResp.RenterSatisfiedPolicies, si.SatisfiedPolicy) @@ -885,12 +895,14 @@ func RPCRefreshContract(ctx context.Context, t TransportClient, tp TxPool, signe // validate the host signature if !existing.HostPublicKey.VerifyHash(renewalSigHash, hostRenewal.HostSignature) { - return RPCRefreshContractResult{}, errors.New("invalid host signature") + return RPCRefreshContractResult{}, errors.New("invalid host renewal signature") + } else if !existing.HostPublicKey.VerifyHash(contractSigHash, hostRenewal.NewContract.HostSignature) { + return RPCRefreshContractResult{}, errors.New("invalid host contract signature") } return RPCRefreshContractResult{ Contract: ContractRevision{ ID: params.ContractID.V2RenewalID(), - Revision: renewal.NewContract, + Revision: hostRenewal.NewContract, }, RenewalSet: TransactionSet{ Basis: hostTransactionSetResp.Basis, diff --git a/rhp/v4/rpc_test.go b/rhp/v4/rpc_test.go index 2f75957..4e939a0 100644 --- a/rhp/v4/rpc_test.go +++ b/rhp/v4/rpc_test.go @@ -156,8 +156,6 @@ func TestSettings(t *testing.T) { WalletAddress: w.Address(), MaxCollateral: types.Siacoins(10000), MaxContractDuration: 1000, - MaxSectorDuration: 3 * 144, - MaxSectorBatchSize: 100, RemainingStorage: 100 * proto4.SectorSize, TotalStorage: 100 * proto4.SectorSize, Prices: proto4.HostPrices{ @@ -216,8 +214,6 @@ func TestFormContract(t *testing.T) { WalletAddress: w.Address(), MaxCollateral: types.Siacoins(10000), MaxContractDuration: 1000, - MaxSectorDuration: 3 * 144, - MaxSectorBatchSize: 100, RemainingStorage: 100 * proto4.SectorSize, TotalStorage: 100 * proto4.SectorSize, Prices: proto4.HostPrices{ @@ -257,6 +253,13 @@ func TestFormContract(t *testing.T) { } else if !known { t.Fatal("expected transaction set to be known") } + + sigHash := cm.TipState().ContractSigHash(result.Contract.Revision) + if !renterKey.PublicKey().VerifyHash(sigHash, result.Contract.Revision.RenterSignature) { + t.Fatal("renter signature verification failed") + } else if !hostKey.PublicKey().VerifyHash(sigHash, result.Contract.Revision.HostSignature) { + t.Fatal("host signature verification failed") + } } func TestFormContractBasis(t *testing.T) { @@ -275,8 +278,6 @@ func TestFormContractBasis(t *testing.T) { WalletAddress: w.Address(), MaxCollateral: types.Siacoins(10000), MaxContractDuration: 1000, - MaxSectorDuration: 3 * 144, - MaxSectorBatchSize: 100, RemainingStorage: 100 * proto4.SectorSize, TotalStorage: 100 * proto4.SectorSize, Prices: proto4.HostPrices{ @@ -333,8 +334,6 @@ func TestRPCRefresh(t *testing.T) { WalletAddress: w.Address(), MaxCollateral: types.Siacoins(10000), MaxContractDuration: 1000, - MaxSectorDuration: 3 * 144, - MaxSectorBatchSize: 100, RemainingStorage: 100 * proto4.SectorSize, TotalStorage: 100 * proto4.SectorSize, Prices: proto4.HostPrices{ @@ -428,6 +427,13 @@ func TestRPCRefresh(t *testing.T) { } else if !known { t.Fatal("expected transaction set to be known") } + + sigHash := cm.TipState().ContractSigHash(refreshResult.Contract.Revision) + if !renterKey.PublicKey().VerifyHash(sigHash, refreshResult.Contract.Revision.RenterSignature) { + t.Fatal("renter signature verification failed") + } else if !hostKey.PublicKey().VerifyHash(sigHash, refreshResult.Contract.Revision.HostSignature) { + t.Fatal("host signature verification failed") + } }) } @@ -446,8 +452,6 @@ func TestRPCRenew(t *testing.T) { WalletAddress: w.Address(), MaxCollateral: types.Siacoins(10000), MaxContractDuration: 1000, - MaxSectorDuration: 3 * 144, - MaxSectorBatchSize: 100, RemainingStorage: 100 * proto4.SectorSize, TotalStorage: 100 * proto4.SectorSize, Prices: proto4.HostPrices{ @@ -483,6 +487,12 @@ func TestRPCRenew(t *testing.T) { t.Fatal(err) } revision := result.Contract + sigHash := cm.TipState().ContractSigHash(revision.Revision) + if !renterKey.PublicKey().VerifyHash(sigHash, revision.Revision.RenterSignature) { + t.Fatal("renter signature verification failed") + } else if !hostKey.PublicKey().VerifyHash(sigHash, revision.Revision.HostSignature) { + t.Fatal("host signature verification failed") + } // verify the transaction set is valid if known, err := cm.AddV2PoolTransactions(result.FormationSet.Basis, result.FormationSet.Transactions); err != nil { @@ -544,6 +554,13 @@ func TestRPCRenew(t *testing.T) { } else if !known { t.Fatal("expected transaction set to be known") } + + sigHash := cm.TipState().ContractSigHash(renewResult.Contract.Revision) + if !renterKey.PublicKey().VerifyHash(sigHash, renewResult.Contract.Revision.RenterSignature) { + t.Fatal("renter signature verification failed") + } else if !hostKey.PublicKey().VerifyHash(sigHash, renewResult.Contract.Revision.HostSignature) { + t.Fatal("host signature verification failed") + } }) t.Run("full rollover", func(t *testing.T) { @@ -566,6 +583,13 @@ func TestRPCRenew(t *testing.T) { } else if !known { t.Fatal("expected transaction set to be known") } + + sigHash := cm.TipState().ContractSigHash(renewResult.Contract.Revision) + if !renterKey.PublicKey().VerifyHash(sigHash, renewResult.Contract.Revision.RenterSignature) { + t.Fatal("renter signature verification failed") + } else if !hostKey.PublicKey().VerifyHash(sigHash, renewResult.Contract.Revision.HostSignature) { + t.Fatal("host signature verification failed") + } }) t.Run("no rollover", func(t *testing.T) { @@ -588,6 +612,13 @@ func TestRPCRenew(t *testing.T) { } else if !known { t.Fatal("expected transaction set to be known") } + + sigHash := cm.TipState().ContractSigHash(renewResult.Contract.Revision) + if !renterKey.PublicKey().VerifyHash(sigHash, renewResult.Contract.Revision.RenterSignature) { + t.Fatal("renter signature verification failed") + } else if !hostKey.PublicKey().VerifyHash(sigHash, renewResult.Contract.Revision.HostSignature) { + t.Fatal("host signature verification failed") + } }) } @@ -607,8 +638,6 @@ func TestAccounts(t *testing.T) { WalletAddress: w.Address(), MaxCollateral: types.Siacoins(10000), MaxContractDuration: 1000, - MaxSectorDuration: 3 * 144, - MaxSectorBatchSize: 100, RemainingStorage: 100 * proto4.SectorSize, TotalStorage: 100 * proto4.SectorSize, Prices: proto4.HostPrices{ @@ -708,8 +737,6 @@ func TestReadWriteSector(t *testing.T) { WalletAddress: w.Address(), MaxCollateral: types.Siacoins(10000), MaxContractDuration: 1000, - MaxSectorDuration: 3 * 144, - MaxSectorBatchSize: 100, RemainingStorage: 100 * proto4.SectorSize, TotalStorage: 100 * proto4.SectorSize, Prices: proto4.HostPrices{ @@ -804,8 +831,6 @@ func TestAppendSectors(t *testing.T) { WalletAddress: w.Address(), MaxCollateral: types.Siacoins(10000), MaxContractDuration: 1000, - MaxSectorDuration: 3 * 144, - MaxSectorBatchSize: 100, RemainingStorage: 100 * proto4.SectorSize, TotalStorage: 100 * proto4.SectorSize, Prices: proto4.HostPrices{ @@ -840,6 +865,27 @@ func TestAppendSectors(t *testing.T) { } revision := formResult.Contract + assertLastRevision := func(t *testing.T) { + t.Helper() + + lastRev, err := rhp4.RPCLatestRevision(context.Background(), transport, revision.ID) + if err != nil { + t.Fatal(err) + } else if !reflect.DeepEqual(lastRev, revision.Revision) { + t.Log(lastRev) + t.Log(revision.Revision) + t.Fatalf("expected last revision to match") + } + + sigHash := cm.TipState().ContractSigHash(revision.Revision) + if !renterKey.PublicKey().VerifyHash(sigHash, lastRev.RenterSignature) { + t.Fatal("renter signature invalid") + } else if !hostKey.PublicKey().VerifyHash(sigHash, lastRev.HostSignature) { + t.Fatal("host signature invalid") + } + } + assertLastRevision(t) + cs := cm.TipState() account := proto4.Account(renterKey.PublicKey()) @@ -851,6 +897,7 @@ func TestAppendSectors(t *testing.T) { t.Fatal(err) } revision.Revision = fundResult.Revision + assertLastRevision(t) token := proto4.AccountToken{ Account: account, @@ -890,6 +937,8 @@ func TestAppendSectors(t *testing.T) { if appendResult.Revision.FileMerkleRoot != proto4.MetaRoot(roots) { t.Fatal("root mismatch") } + revision.Revision = appendResult.Revision + assertLastRevision(t) // read the sectors back buf := bytes.NewBuffer(make([]byte, 0, proto4.SectorSize)) @@ -921,8 +970,6 @@ func TestVerifySector(t *testing.T) { WalletAddress: w.Address(), MaxCollateral: types.Siacoins(10000), MaxContractDuration: 1000, - MaxSectorDuration: 3 * 144, - MaxSectorBatchSize: 100, RemainingStorage: 100 * proto4.SectorSize, TotalStorage: 100 * proto4.SectorSize, Prices: proto4.HostPrices{ @@ -1014,8 +1061,6 @@ func TestRPCFreeSectors(t *testing.T) { WalletAddress: w.Address(), MaxCollateral: types.Siacoins(10000), MaxContractDuration: 1000, - MaxSectorDuration: 3 * 144, - MaxSectorBatchSize: 100, RemainingStorage: 100 * proto4.SectorSize, TotalStorage: 100 * proto4.SectorSize, Prices: proto4.HostPrices{ @@ -1137,8 +1182,6 @@ func TestRPCSectorRoots(t *testing.T) { WalletAddress: w.Address(), MaxCollateral: types.Siacoins(10000), MaxContractDuration: 1000, - MaxSectorDuration: 3 * 144, - MaxSectorBatchSize: 100, RemainingStorage: 100 * proto4.SectorSize, TotalStorage: 100 * proto4.SectorSize, Prices: proto4.HostPrices{ @@ -1247,8 +1290,6 @@ func BenchmarkWrite(b *testing.B) { WalletAddress: w.Address(), MaxCollateral: types.Siacoins(10000), MaxContractDuration: 1000, - MaxSectorDuration: 3 * 144, - MaxSectorBatchSize: 100, RemainingStorage: 100 * proto4.SectorSize, TotalStorage: 100 * proto4.SectorSize, Prices: proto4.HostPrices{ @@ -1337,8 +1378,6 @@ func BenchmarkRead(b *testing.B) { WalletAddress: w.Address(), MaxCollateral: types.Siacoins(10000), MaxContractDuration: 1000, - MaxSectorDuration: 3 * 144, - MaxSectorBatchSize: 100, RemainingStorage: 100 * proto4.SectorSize, TotalStorage: 100 * proto4.SectorSize, Prices: proto4.HostPrices{ @@ -1439,8 +1478,6 @@ func BenchmarkContractUpload(b *testing.B) { WalletAddress: w.Address(), MaxCollateral: types.Siacoins(10000), MaxContractDuration: 1000, - MaxSectorDuration: 3 * 144, - MaxSectorBatchSize: 100, RemainingStorage: 100 * proto4.SectorSize, TotalStorage: 100 * proto4.SectorSize, Prices: proto4.HostPrices{ diff --git a/rhp/v4/server.go b/rhp/v4/server.go index af45916..16f6ec2 100644 --- a/rhp/v4/server.go +++ b/rhp/v4/server.go @@ -17,17 +17,6 @@ import ( "lukechampine.com/frand" ) -const ( - sectorsPerTiB = (1 << 40) / (1 << 22) - memoryPer1TiB = sectorsPerTiB * 32 - - sectorsPer10TiB = 10 * sectorsPerTiB - memoryPer10TiB = sectorsPer10TiB * 32 - - sectorsPer100TiB = 100 * sectorsPerTiB - memoryPer100TiB = sectorsPer100TiB * 32 -) - var protocolVersion = [3]byte{4, 0, 0} type ( @@ -232,9 +221,7 @@ func (s *Server) handleRPCWriteSector(stream net.Conn) error { var req rhp4.RPCWriteSectorRequest if err := rhp4.ReadRequest(stream, &req); err != nil { return errorDecodingError("failed to read request: %v", err) - } - settings := s.settings.RHP4Settings() - if err := req.Validate(s.hostKey.PublicKey(), settings.MaxSectorDuration); err != nil { + } else if err := req.Validate(s.hostKey.PublicKey()); err != nil { return errorBadRequest("request invalid: %v", err) } prices := req.Prices @@ -253,12 +240,12 @@ func (s *Server) handleRPCWriteSector(stream net.Conn) error { return errorDecodingError("failed to read sector data: %v", err) } - usage := prices.RPCWriteSectorCost(req.DataLength, req.Duration) + usage := prices.RPCWriteSectorCost(req.DataLength) if err = s.contractor.DebitAccount(req.Token.Account, usage); err != nil { return fmt.Errorf("failed to debit account: %w", err) } - if err := s.sectors.StoreSector(root, §or, req.Duration); err != nil { + if err := s.sectors.StoreSector(root, §or, prices.TipHeight+rhp4.TempSectorDuration); err != nil { return fmt.Errorf("failed to store sector: %w", err) } return rhp4.WriteResponse(stream, &rhp4.RPCWriteSectorResponse{ @@ -283,9 +270,7 @@ func (s *Server) handleRPCFreeSectors(stream net.Conn) error { } fc := state.Revision - - settings := s.settings.RHP4Settings() - if err := req.Validate(s.hostKey.PublicKey(), fc, settings.MaxSectorBatchSize); err != nil { + if err := req.Validate(s.hostKey.PublicKey(), fc); err != nil { return errorBadRequest("request invalid: %v", err) } prices := req.Prices @@ -347,8 +332,7 @@ func (s *Server) handleRPCAppendSectors(stream net.Conn) error { return errorDecodingError("failed to read request: %v", err) } - settings := s.settings.RHP4Settings() - if err := req.Validate(s.hostKey.PublicKey(), settings.MaxSectorBatchSize); err != nil { + if err := req.Validate(s.hostKey.PublicKey()); err != nil { return errorBadRequest("request invalid: %v", err) } @@ -440,6 +424,7 @@ func (s *Server) handleRPCFundAccounts(stream net.Conn) error { if !revision.RenterPublicKey.VerifyHash(sigHash, req.RenterSignature) { return rhp4.ErrInvalidSignature } + revision.RenterSignature = req.RenterSignature revision.HostSignature = s.hostKey.SignHash(sigHash) balances, err := s.contractor.CreditAccountsWithContract(req.Deposits, req.ContractID, revision, usage) @@ -483,8 +468,7 @@ func (s *Server) handleRPCSectorRoots(stream net.Conn) error { defer unlock() // validate the request fields - settings := s.settings.RHP4Settings() - if err := req.Validate(s.hostKey.PublicKey(), state.Revision, settings.MaxSectorBatchSize); err != nil { + if err := req.Validate(s.hostKey.PublicKey(), state.Revision); err != nil { return rhp4.NewRPCError(rhp4.ErrorCodeBadRequest, err.Error()) } prices := req.Prices @@ -504,6 +488,7 @@ func (s *Server) handleRPCSectorRoots(stream net.Conn) error { // sign the revision revision.HostSignature = s.hostKey.SignHash(sigHash) + revision.RenterSignature = req.RenterSignature // update the contract err = s.contractor.ReviseV2Contract(req.ContractID, revision, state.Roots, usage) @@ -693,7 +678,7 @@ func (s *Server) handleRPCRefreshContract(stream net.Conn) error { // validate the request settings := s.settings.RHP4Settings() - if err := req.Validate(s.hostKey.PublicKey(), state.Revision.ExpirationHeight, settings.MaxCollateral); err != nil { + if err := req.Validate(s.hostKey.PublicKey(), state.Revision.TotalCollateral, state.Revision.ExpirationHeight, settings.MaxCollateral); err != nil { return rhp4.NewRPCError(rhp4.ErrorCodeBadRequest, err.Error()) } @@ -782,15 +767,22 @@ func (s *Server) handleRPCRefreshContract(stream net.Conn) error { // validate the renter's signature renewalSigHash := cs.RenewalSigHash(renewal) if !existing.RenterPublicKey.VerifyHash(renewalSigHash, renterSigResp.RenterRenewalSignature) { - return rhp4.ErrInvalidSignature + return fmt.Errorf("failed to validate renter renewal signature: %w", rhp4.ErrInvalidSignature) } renewal.RenterSignature = renterSigResp.RenterRenewalSignature + renewal.HostSignature = s.hostKey.SignHash(renewalSigHash) + + contractSigHash := cs.ContractSigHash(renewal.NewContract) + if !existing.RenterPublicKey.VerifyHash(contractSigHash, renterSigResp.RenterContractSignature) { + return fmt.Errorf("failed to validate renter contract signature: %w", rhp4.ErrInvalidSignature) + } + renewal.NewContract.RenterSignature = renterSigResp.RenterContractSignature + renewal.NewContract.HostSignature = s.hostKey.SignHash(contractSigHash) // apply the renter's signatures for i, policy := range renterSigResp.RenterSatisfiedPolicies { renewalTxn.SiacoinInputs[i].SatisfiedPolicy = policy } - renewal.HostSignature = s.hostKey.SignHash(renewalSigHash) // add the renter's parents to our transaction pool to ensure they are valid // and update the proofs. @@ -849,7 +841,7 @@ func (s *Server) handleRPCRenewContract(stream net.Conn) error { tip := s.chain.Tip() // validate the request - if err := req.Validate(s.hostKey.PublicKey(), tip, state.Revision.ProofHeight, settings.MaxCollateral, settings.MaxContractDuration); err != nil { + if err := req.Validate(s.hostKey.PublicKey(), tip, state.Revision.Filesize, state.Revision.ProofHeight, settings.MaxCollateral, settings.MaxContractDuration); err != nil { return rhp4.NewRPCError(rhp4.ErrorCodeBadRequest, err.Error()) } @@ -944,15 +936,22 @@ func (s *Server) handleRPCRenewContract(stream net.Conn) error { // validate the renter's signature renewalSigHash := cs.RenewalSigHash(renewal) if !existing.RenterPublicKey.VerifyHash(renewalSigHash, renterSigResp.RenterRenewalSignature) { - return rhp4.ErrInvalidSignature + return fmt.Errorf("failed to validate renewal signature: %w", rhp4.ErrInvalidSignature) } renewal.RenterSignature = renterSigResp.RenterRenewalSignature + renewal.HostSignature = s.hostKey.SignHash(renewalSigHash) + + contractSighash := cs.ContractSigHash(renewal.NewContract) + if !existing.RenterPublicKey.VerifyHash(contractSighash, renterSigResp.RenterContractSignature) { + return fmt.Errorf("failed to validate contract signature: %w", rhp4.ErrInvalidSignature) + } + renewal.NewContract.RenterSignature = renterSigResp.RenterContractSignature + renewal.NewContract.HostSignature = s.hostKey.SignHash(contractSighash) // apply the renter's signatures for i, policy := range renterSigResp.RenterSatisfiedPolicies { renewalTxn.SiacoinInputs[i].SatisfiedPolicy = policy } - renewal.HostSignature = s.hostKey.SignHash(renewalSigHash) // add the renter's parents to our transaction pool to ensure they are valid // and update the proofs. diff --git a/syncer/peer.go b/syncer/peer.go index 9c5c938..45a6d81 100644 --- a/syncer/peer.go +++ b/syncer/peer.go @@ -117,7 +117,7 @@ func (p *Peer) SendBlock(id types.BlockID, timeout time.Duration) (types.Block, } // RelayHeader relays a header to the peer. -func (p *Peer) RelayHeader(h gateway.BlockHeader, timeout time.Duration) error { +func (p *Peer) RelayHeader(h types.BlockHeader, timeout time.Duration) error { return p.callRPC(&gateway.RPCRelayHeader{Header: h}, timeout) } @@ -182,7 +182,7 @@ func (p *Peer) SendCheckpoint(index types.ChainIndex, timeout time.Duration) (ty } // RelayV2Header relays a v2 block header to the peer. -func (p *Peer) RelayV2Header(h gateway.V2BlockHeader, timeout time.Duration) error { +func (p *Peer) RelayV2Header(h types.BlockHeader, timeout time.Duration) error { return p.callRPC(&gateway.RPCRelayV2Header{Header: h}, timeout) } @@ -381,17 +381,17 @@ func (s *Syncer) handleRPC(id types.Specifier, stream *gateway.Stream, origin *P if err := stream.ReadRequest(r); err != nil { return err } - cs, ok := s.cm.State(r.Header.Parent.ID) + cs, ok := s.cm.State(r.Header.ParentID) if !ok { - s.resync(origin, fmt.Sprintf("peer relayed a v2 header with unknown parent (%v)", r.Header.Parent.ID)) + s.resync(origin, fmt.Sprintf("peer relayed a v2 header with unknown parent (%v)", r.Header.ParentID)) return nil } - bid := r.Header.ID(cs) + bid := r.Header.ID() if _, ok := s.cm.State(bid); ok { return nil // already seen } else if bid.CmpWork(cs.ChildTarget) < 0 { return s.ban(origin, errors.New("peer sent v2 header with insufficient work")) - } else if r.Header.Parent != s.cm.Tip() { + } else if r.Header.ParentID != s.cm.Tip().ID { // block extends a sidechain, which peer (if honest) believes to be the // heaviest chain s.resync(origin, "peer relayed a v2 header that does not attach to our tip") diff --git a/syncer/syncer.go b/syncer/syncer.go index 83f9f64..f1ac29b 100644 --- a/syncer/syncer.go +++ b/syncer/syncer.go @@ -301,7 +301,7 @@ func (s *Syncer) runPeer(p *Peer) error { } } -func (s *Syncer) relayHeader(h gateway.BlockHeader, origin *Peer) { +func (s *Syncer) relayHeader(h types.BlockHeader, origin *Peer) { s.mu.Lock() defer s.mu.Unlock() for _, p := range s.peers { @@ -323,7 +323,7 @@ func (s *Syncer) relayTransactionSet(txns []types.Transaction, origin *Peer) { } } -func (s *Syncer) relayV2Header(bh gateway.V2BlockHeader, origin *Peer) { +func (s *Syncer) relayV2Header(bh types.BlockHeader, origin *Peer) { s.mu.Lock() defer s.mu.Unlock() for _, p := range s.peers { @@ -723,10 +723,10 @@ func (s *Syncer) Connect(ctx context.Context, addr string) (*Peer, error) { } // BroadcastHeader broadcasts a header to all peers. -func (s *Syncer) BroadcastHeader(h gateway.BlockHeader) { s.relayHeader(h, nil) } +func (s *Syncer) BroadcastHeader(h types.BlockHeader) { s.relayHeader(h, nil) } // BroadcastV2Header broadcasts a v2 header to all peers. -func (s *Syncer) BroadcastV2Header(h gateway.V2BlockHeader) { s.relayV2Header(h, nil) } +func (s *Syncer) BroadcastV2Header(h types.BlockHeader) { s.relayV2Header(h, nil) } // BroadcastV2BlockOutline broadcasts a v2 block outline to all peers. func (s *Syncer) BroadcastV2BlockOutline(b gateway.V2BlockOutline) { s.relayV2BlockOutline(b, nil) } diff --git a/syncer/syncer_test.go b/syncer/syncer_test.go index 89d8502..d3551a0 100644 --- a/syncer/syncer_test.go +++ b/syncer/syncer_test.go @@ -80,12 +80,7 @@ func TestSyncer(t *testing.T) { } // broadcast the tip from s1 to s2 - s1.BroadcastHeader(gateway.BlockHeader{ - ParentID: b.ParentID, - Nonce: b.Nonce, - Timestamp: b.Timestamp, - MerkleRoot: b.MerkleRoot(), - }) + s1.BroadcastHeader(b.Header()) for i := 0; i < 100; i++ { if cm1.Tip() == cm2.Tip() { diff --git a/testutil/host.go b/testutil/host.go index 87cda42..c0af3f9 100644 --- a/testutil/host.go +++ b/testutil/host.go @@ -7,6 +7,7 @@ import ( "sync" "testing" + "go.sia.tech/core/consensus" proto4 "go.sia.tech/core/rhp/v4" "go.sia.tech/core/types" "go.sia.tech/coreutils/chain" @@ -119,6 +120,13 @@ func (ec *EphemeralContractor) AddV2Contract(formationSet rhp4.TransactionSet, _ } fc := formationTxn.FileContracts[0] + sigHash := consensus.State{}.ContractSigHash(fc) + if !fc.RenterPublicKey.VerifyHash(sigHash, fc.RenterSignature) { + return errors.New("invalid renter signature") + } else if !fc.HostPublicKey.VerifyHash(sigHash, fc.HostSignature) { + return errors.New("invalid host signature") + } + contractID := formationTxn.V2FileContractID(formationTxn.ID(), 0) if _, ok := ec.contracts[contractID]; ok { return errors.New("contract already exists") @@ -160,7 +168,14 @@ func (ec *EphemeralContractor) RenewV2Contract(renewalSet rhp4.TransactionSet, _ return errors.New("contract already exists") } - ec.contracts[existingID] = renewal.FinalRevision + sigHash := consensus.State{}.ContractSigHash(renewal.NewContract) + if !existing.RenterPublicKey.VerifyHash(sigHash, renewal.NewContract.RenterSignature) { + return errors.New("invalid renter signature") + } else if !existing.HostPublicKey.VerifyHash(sigHash, renewal.NewContract.HostSignature) { + 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 @@ -179,6 +194,13 @@ func (ec *EphemeralContractor) ReviseV2Contract(contractID types.FileContractID, return errors.New("revision number must be greater than existing") } + sigHash := consensus.State{}.ContractSigHash(revision) + if !existing.RenterPublicKey.VerifyHash(sigHash, revision.RenterSignature) { + return errors.New("invalid renter signature") + } else if !existing.HostPublicKey.VerifyHash(sigHash, revision.HostSignature) { + return errors.New("invalid host signature") + } + ec.contracts[contractID] = revision ec.roots[contractID] = append([]types.Hash256(nil), roots...) return nil @@ -202,11 +224,26 @@ func (ec *EphemeralContractor) CreditAccountsWithContract(deposits []proto4.Acco ec.mu.Lock() defer ec.mu.Unlock() + existing, ok := ec.contracts[contractID] + if !ok { + return nil, errors.New("contract not found") + } else if revision.RevisionNumber <= existing.RevisionNumber { + return nil, errors.New("revision number must be greater than existing") + } + + sigHash := consensus.State{}.ContractSigHash(revision) + if !existing.RenterPublicKey.VerifyHash(sigHash, revision.RenterSignature) { + return nil, errors.New("invalid renter signature") + } else if !existing.HostPublicKey.VerifyHash(sigHash, revision.HostSignature) { + return nil, errors.New("invalid host signature") + } + var balance = make([]types.Currency, 0, len(deposits)) for _, deposit := range deposits { ec.accounts[deposit.Account] = ec.accounts[deposit.Account].Add(deposit.Amount) balance = append(balance, ec.accounts[deposit.Account]) } + ec.contracts[contractID] = revision return balance, nil } diff --git a/wallet/wallet_test.go b/wallet/wallet_test.go index 152d185..abb82ed 100644 --- a/wallet/wallet_test.go +++ b/wallet/wallet_test.go @@ -3,7 +3,6 @@ package wallet_test import ( "errors" "fmt" - "math" "math/bits" "path/filepath" "testing" @@ -1728,14 +1727,10 @@ func TestSingleAddressWalletEventTypes(t *testing.T) { cau.UpdateElementProof(&fce.StateElement) } - // finalize the contract - finalRevision := fce.V2FileContract - finalRevision.RevisionNumber = math.MaxUint64 - finalRevision.RenterSignature = types.Signature{} - finalRevision.HostSignature = types.Signature{} // create a renewal renewal := types.V2FileContractRenewal{ - FinalRevision: finalRevision, + FinalRenterOutput: fce.V2FileContract.RenterOutput, + FinalHostOutput: fce.V2FileContract.HostOutput, NewContract: types.V2FileContract{ RenterOutput: fc.RenterOutput, ProofHeight: fc.ProofHeight + 10, @@ -1750,6 +1745,10 @@ func TestSingleAddressWalletEventTypes(t *testing.T) { renewalSig := pk.SignHash(renewalSigHash) renewal.RenterSignature = renewalSig renewal.HostSignature = renewalSig + contractSigHash := cm.TipState().ContractSigHash(renewal.NewContract) + contractSig := pk.SignHash(contractSigHash) + renewal.NewContract.RenterSignature = contractSig + renewal.NewContract.HostSignature = contractSig newContractValue := renterPayout.Add(cm.TipState().V2FileContractTax(renewal.NewContract)) @@ -1792,91 +1791,6 @@ func TestSingleAddressWalletEventTypes(t *testing.T) { mineAndSync(t, cm, ws, wm, types.VoidAddress, 1) assertEvent(t, wm, types.Hash256(types.FileContractID(fce.ID).V2RenterOutputID()), wallet.EventTypeV2ContractResolution, renterPayout, types.ZeroCurrency, cm.Tip().Height+network.MaturityDelay) }) - - t.Run("v2 contract resolution - finalization", func(t *testing.T) { - // create a storage contract - renterPayout := types.Siacoins(10000) - fc := types.V2FileContract{ - RenterOutput: types.SiacoinOutput{ - Address: addr, - Value: renterPayout, - }, - HostOutput: types.SiacoinOutput{ - Address: types.VoidAddress, - Value: types.ZeroCurrency, - }, - ProofHeight: cm.TipState().Index.Height + 10, - ExpirationHeight: cm.TipState().Index.Height + 20, - - RenterPublicKey: pk.PublicKey(), - HostPublicKey: pk.PublicKey(), - } - contractValue := renterPayout.Add(cm.TipState().V2FileContractTax(fc)) - sigHash := cm.TipState().ContractSigHash(fc) - sig := pk.SignHash(sigHash) - fc.RenterSignature = sig - fc.HostSignature = sig - - // create a transaction with the contract - txn := types.V2Transaction{ - FileContracts: []types.V2FileContract{fc}, - } - basis, toSign, err := wm.FundV2Transaction(&txn, contractValue, false) - if err != nil { - t.Fatal(err) - } - wm.SignV2Inputs(&txn, toSign) - - // broadcast the transaction - if _, err := cm.AddV2PoolTransactions(basis, []types.V2Transaction{txn}); err != nil { - t.Fatal(err) - } - // current tip - tip := cm.Tip() - // mine a block to confirm the contract formation - mineAndSync(t, cm, ws, wm, types.VoidAddress, 1) - - // this is annoying because we have to keep the file contract - // proof - _, applied, err := cm.UpdatesSince(tip, 1000) - if err != nil { - t.Fatal(err) - } - - // get the confirmed file contract element - var fce types.V2FileContractElement - applied[0].ForEachV2FileContractElement(func(ele types.V2FileContractElement, _ bool, _ *types.V2FileContractElement, _ types.V2FileContractResolutionType) { - fce = ele - }) - for _, cau := range applied { - cau.UpdateElementProof(&fce.StateElement) - } - - // finalize the contract - finalRevision := fce.V2FileContract - finalRevision.RevisionNumber = math.MaxUint64 - finalRevisionSigHash := cm.TipState().ContractSigHash(finalRevision) - // create a renewal - finalization := types.V2FileContractFinalization(pk.SignHash(finalRevisionSigHash)) - - // create the renewal transaction - resolutionTxn := types.V2Transaction{ - FileContractResolutions: []types.V2FileContractResolution{ - { - Parent: fce, - Resolution: &finalization, - }, - }, - } - - // broadcast the renewal - if _, err := cm.AddV2PoolTransactions(cm.Tip(), []types.V2Transaction{resolutionTxn}); err != nil { - t.Fatal(err) - } - // mine a block to confirm the renewal - mineAndSync(t, cm, ws, wm, types.VoidAddress, 1) - assertEvent(t, wm, types.Hash256(types.FileContractID(fce.ID).V2RenterOutputID()), wallet.EventTypeV2ContractResolution, renterPayout, types.ZeroCurrency, cm.Tip().Height+network.MaturityDelay) - }) } func TestV2TPoolRace(t *testing.T) {