-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 47856c8
Showing
51 changed files
with
12,651 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright (c) 2022 The Sia Foundation | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# [![Sia](https://sia.tech/banners/sia-banner-leaves.png)](http://sia.tech) | ||
|
||
[![GoDoc](https://godoc.org/go.sia.tech/renterd?status.svg)](https://godoc.org/go.sia.tech/renterd) | ||
|
||
A renter for Sia. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package api | ||
|
||
import ( | ||
"go.sia.tech/renterd/internal/consensus" | ||
rhpv2 "go.sia.tech/renterd/rhp/v2" | ||
rhpv3 "go.sia.tech/renterd/rhp/v3" | ||
"go.sia.tech/renterd/slab" | ||
"go.sia.tech/siad/types" | ||
) | ||
|
||
// WalletBalanceResponse is the response to /wallet/balance. | ||
type WalletBalanceResponse struct { | ||
Siacoins types.Currency `json:"siacoins"` | ||
} | ||
|
||
// A SyncerPeer is a unique peer that is being used by the syncer. | ||
type SyncerPeer struct { | ||
NetAddress string `json:"netAddress"` | ||
} | ||
|
||
// A SyncerConnectRequest requests that the syncer connect to a peer. | ||
type SyncerConnectRequest struct { | ||
NetAddress string `json:"netAddress"` | ||
} | ||
|
||
// An RHPScanRequest contains the address and pubkey of the host to scan. | ||
type RHPScanRequest struct { | ||
HostKey consensus.PublicKey `json:"hostKey"` | ||
HostIP string `json:"hostIP"` | ||
} | ||
|
||
// An RHPPrepareFormRequest prepares a new file contract. | ||
type RHPPrepareFormRequest struct { | ||
RenterKey consensus.PrivateKey `json:"renterKey"` | ||
HostKey consensus.PublicKey `json:"hostKey"` | ||
RenterFunds types.Currency `json:"renterFunds"` | ||
HostCollateral types.Currency `json:"hostCollateral"` | ||
EndHeight uint64 `json:"endHeight"` | ||
HostSettings rhpv2.HostSettings `json:"hostSettings"` | ||
} | ||
|
||
// An RHPPrepareFormResponse is the response to /rhp/prepare/form. | ||
type RHPPrepareFormResponse struct { | ||
TransactionSet []types.Transaction `json:"transactionSet"` | ||
} | ||
|
||
// An RHPFormRequest requests that the host create a contract. | ||
type RHPFormRequest struct { | ||
RenterKey consensus.PrivateKey `json:"renterKey"` | ||
HostKey consensus.PublicKey `json:"hostKey"` | ||
HostIP string `json:"hostIP"` | ||
TransactionSet []types.Transaction `json:"transactionSet"` | ||
WalletKey consensus.PrivateKey `json:"walletKey"` | ||
} | ||
|
||
// An RHPFormResponse is the response to /rhp/form. It contains the formed | ||
// contract and its transaction set. | ||
type RHPFormResponse struct { | ||
Contract rhpv2.Contract `json:"contract"` | ||
TransactionSet []types.Transaction `json:"transactionSet"` | ||
} | ||
|
||
// An RHPPrepareRenewRequest prepares a file contract for renewal. | ||
type RHPPrepareRenewRequest struct { | ||
Contract types.FileContractRevision `json:"contract"` | ||
RenterKey consensus.PrivateKey `json:"renterKey"` | ||
HostKey consensus.PublicKey `json:"hostKey"` | ||
RenterFunds types.Currency `json:"renterFunds"` | ||
HostCollateral types.Currency `json:"hostCollateral"` | ||
EndHeight uint64 `json:"endHeight"` | ||
HostSettings rhpv2.HostSettings `json:"hostSettings"` | ||
} | ||
|
||
// An RHPPrepareRenewResponse is the response to /rhp/prepare/renew. | ||
type RHPPrepareRenewResponse struct { | ||
TransactionSet []types.Transaction `json:"transactionSet"` | ||
FinalPayment types.Currency `json:"finalPayment"` | ||
} | ||
|
||
// An RHPRenewRequest requests that the host renew a contract. | ||
type RHPRenewRequest struct { | ||
RenterKey consensus.PrivateKey `json:"renterKey"` | ||
HostKey consensus.PublicKey `json:"hostKey"` | ||
HostIP string `json:"hostIP"` | ||
ContractID types.FileContractID `json:"contractID"` | ||
TransactionSet []types.Transaction `json:"transactionSet"` | ||
FinalPayment types.Currency `json:"finalPayment"` | ||
WalletKey consensus.PrivateKey `json:"walletKey"` | ||
} | ||
|
||
// An RHPRenewResponse is the response to /rhp/renew. It contains the renewed | ||
// contract and its transaction set. | ||
type RHPRenewResponse struct { | ||
Contract rhpv2.Contract `json:"contract"` | ||
TransactionSet []types.Transaction `json:"transactionSet"` | ||
} | ||
|
||
// An RHPFundRequest funds an ephemeral account. | ||
type RHPFundRequest struct { | ||
Contract types.FileContractRevision `json:"contract"` | ||
RenterKey consensus.PrivateKey `json:"renterKey"` | ||
HostKey consensus.PublicKey `json:"hostKey"` | ||
HostIP string `json:"hostIP"` | ||
Account rhpv3.Account `json:"account"` | ||
Amount types.Currency `json:"amount"` | ||
} | ||
|
||
// An RHPPaymentRequest creates a payment by spending value in an ephemeral | ||
// account. | ||
type RHPPaymentRequest struct { | ||
Account rhpv3.Account `json:"account"` | ||
Amount types.Currency `json:"amount"` | ||
Expiry uint64 `json:"expiry"` | ||
AccountKey consensus.PrivateKey `json:"accountKey"` | ||
} | ||
|
||
type Contract struct { | ||
HostKey consensus.PublicKey `json:"hostKey"` | ||
HostIP string `json:"hostIP"` | ||
ID types.FileContractID `json:"id"` | ||
RenterKey consensus.PrivateKey `json:"renterKey"` | ||
} | ||
|
||
type SlabsUploadRequest struct { | ||
MinShards uint8 `json:"minShards"` | ||
TotalShards uint8 `json:"totalShards"` | ||
Contracts []Contract `json:"contracts"` | ||
} | ||
|
||
type SlabsDownloadRequest struct { | ||
Slabs []slab.SlabSlice `json:"slabs"` | ||
Offset int64 `json:"offset"` | ||
Length int64 `json:"length"` | ||
Contracts []Contract `json:"contracts"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
package api_test | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"io" | ||
"net" | ||
"net/http" | ||
"testing" | ||
|
||
"go.sia.tech/renterd/api" | ||
"go.sia.tech/renterd/internal/consensus" | ||
"go.sia.tech/renterd/internal/contractsutil" | ||
"go.sia.tech/renterd/internal/hostdbutil" | ||
"go.sia.tech/renterd/internal/objectutil" | ||
"go.sia.tech/renterd/internal/slabutil" | ||
"go.sia.tech/renterd/internal/walletutil" | ||
"go.sia.tech/renterd/object" | ||
rhpv2 "go.sia.tech/renterd/rhp/v2" | ||
rhpv3 "go.sia.tech/renterd/rhp/v3" | ||
"go.sia.tech/renterd/slab" | ||
"go.sia.tech/renterd/wallet" | ||
"go.sia.tech/siad/types" | ||
"lukechampine.com/frand" | ||
) | ||
|
||
type mockChainManager struct{} | ||
|
||
func (mockChainManager) TipState() (cs consensus.State) { return } | ||
|
||
type mockSyncer struct{} | ||
|
||
func (mockSyncer) Addr() string { return "" } | ||
func (mockSyncer) Peers() []string { return nil } | ||
func (mockSyncer) Connect(addr string) error { return nil } | ||
func (mockSyncer) BroadcastTransaction(txn types.Transaction, dependsOn []types.Transaction) { | ||
} | ||
|
||
type mockTxPool struct{} | ||
|
||
func (mockTxPool) RecommendedFee() types.Currency { return types.ZeroCurrency } | ||
func (mockTxPool) Transactions() []types.Transaction { return nil } | ||
func (mockTxPool) AddTransactionSet(txns []types.Transaction) error { return nil } | ||
func (mockTxPool) UnconfirmedParents(txn types.Transaction) ([]types.Transaction, error) { | ||
return nil, nil | ||
} | ||
|
||
type mockRHP struct{} | ||
|
||
func (mockRHP) Settings(ctx context.Context, hostIP string, hostKey consensus.PublicKey) (rhpv2.HostSettings, error) { | ||
return rhpv2.HostSettings{}, nil | ||
} | ||
|
||
func (mockRHP) FormContract(ctx context.Context, cs consensus.State, hostIP string, hostKey consensus.PublicKey, renterKey consensus.PrivateKey, txns []types.Transaction, walletKey consensus.PrivateKey) (rhpv2.Contract, []types.Transaction, error) { | ||
txn := txns[len(txns)-1] | ||
fc := txn.FileContracts[0] | ||
return rhpv2.Contract{ | ||
Revision: types.FileContractRevision{ | ||
ParentID: txn.FileContractID(0), | ||
UnlockConditions: types.UnlockConditions{ | ||
PublicKeys: []types.SiaPublicKey{ | ||
{Algorithm: types.SignatureEd25519, Key: renterKey[:]}, | ||
{Algorithm: types.SignatureEd25519, Key: hostKey[:]}, | ||
}, | ||
SignaturesRequired: 2, | ||
}, | ||
NewRevisionNumber: 1, | ||
NewFileSize: fc.FileSize, | ||
NewFileMerkleRoot: fc.FileMerkleRoot, | ||
NewWindowStart: fc.WindowStart, | ||
NewWindowEnd: fc.WindowEnd, | ||
NewValidProofOutputs: fc.ValidProofOutputs, | ||
NewMissedProofOutputs: fc.MissedProofOutputs, | ||
NewUnlockHash: fc.UnlockHash, | ||
}, | ||
}, nil, nil | ||
} | ||
|
||
func (mockRHP) RenewContract(ctx context.Context, cs consensus.State, hostIP string, hostKey consensus.PublicKey, renterKey consensus.PrivateKey, contractID types.FileContractID, txns []types.Transaction, finalPayment types.Currency, walletKey consensus.PrivateKey) (rhpv2.Contract, []types.Transaction, error) { | ||
return rhpv2.Contract{}, nil, nil | ||
} | ||
|
||
func (mockRHP) FundAccount(ctx context.Context, hostIP string, hostKey consensus.PublicKey, contract types.FileContractRevision, renterKey consensus.PrivateKey, account rhpv3.Account, amount types.Currency) (rhpv2.Contract, error) { | ||
return rhpv2.Contract{}, nil | ||
} | ||
|
||
func (mockRHP) ReadRegistry(ctx context.Context, hostIP string, hostKey consensus.PublicKey, payment rhpv3.PaymentMethod, registryKey rhpv3.RegistryKey) (rhpv3.RegistryValue, error) { | ||
return rhpv3.RegistryValue{}, nil | ||
} | ||
|
||
func (mockRHP) UpdateRegistry(ctx context.Context, hostIP string, hostKey consensus.PublicKey, payment rhpv3.PaymentMethod, registryKey rhpv3.RegistryKey, registryValue rhpv3.RegistryValue) error { | ||
return nil | ||
} | ||
|
||
type mockSlabMover struct { | ||
hs *slabutil.MockHostSet | ||
} | ||
|
||
func (sm mockSlabMover) UploadSlabs(ctx context.Context, r io.Reader, m, n uint8, currentHeight uint64, contracts []api.Contract) ([]slab.Slab, error) { | ||
ssu := slab.SerialSlabsUploader{SlabUploader: sm.hs.SlabUploader()} | ||
return ssu.UploadSlabs(ctx, slab.NewUniformSlabReader(r, m, n)) | ||
} | ||
|
||
func (sm mockSlabMover) DownloadSlabs(ctx context.Context, w io.Writer, slabs []slab.SlabSlice, offset, length int64, currentHeight uint64, contracts []api.Contract) error { | ||
ssd := slab.SerialSlabsDownloader{SlabDownloader: sm.hs.SlabDownloader()} | ||
return ssd.DownloadSlabs(ctx, w, slabs, offset, length) | ||
} | ||
|
||
type node struct { | ||
w *wallet.SingleAddressWallet | ||
hdb *hostdbutil.EphemeralDB | ||
cs *contractsutil.EphemeralStore | ||
os *objectutil.EphemeralStore | ||
sm mockSlabMover | ||
|
||
walletKey consensus.PrivateKey | ||
} | ||
|
||
func (n *node) addHost() consensus.PublicKey { | ||
return n.sm.hs.AddHost() | ||
} | ||
|
||
func newTestNode() *node { | ||
walletKey := consensus.GeneratePrivateKey() | ||
w := wallet.NewSingleAddressWallet(walletKey, walletutil.NewEphemeralStore(wallet.StandardAddress(walletKey.PublicKey()))) | ||
hdb := hostdbutil.NewEphemeralDB() | ||
cs := contractsutil.NewEphemeralStore() | ||
os := objectutil.NewEphemeralStore() | ||
sm := mockSlabMover{hs: slabutil.NewMockHostSet()} | ||
return &node{w, hdb, cs, os, sm, walletKey} | ||
} | ||
|
||
func runServer(n *node) (*api.Client, func()) { | ||
l, err := net.Listen("tcp", ":0") | ||
if err != nil { | ||
panic(err) | ||
} | ||
go func() { | ||
srv := api.NewServer(mockChainManager{}, mockSyncer{}, mockTxPool{}, n.w, n.hdb, mockRHP{}, n.cs, n.sm, n.os) | ||
http.Serve(l, api.AuthMiddleware(srv, "password")) | ||
}() | ||
c := api.NewClient("http://"+l.Addr().String(), "password") | ||
return c, func() { l.Close() } | ||
} | ||
|
||
func TestSlabs(t *testing.T) { | ||
n := newTestNode() | ||
c, shutdown := runServer(n) | ||
defer shutdown() | ||
|
||
hosts := make([]consensus.PublicKey, 3) | ||
for i := range hosts { | ||
hosts[i] = n.addHost() | ||
} | ||
|
||
// form contracts | ||
var contracts []api.Contract | ||
for _, hostKey := range hosts { | ||
const hostIP = "" | ||
settings, err := c.RHPScan(hostKey, hostIP) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
renterKey := consensus.GeneratePrivateKey() | ||
txns, err := c.RHPPrepareForm(renterKey, hostKey, types.ZeroCurrency, types.ZeroCurrency, 0, settings) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
c, _, err := c.RHPForm(renterKey, hostKey, hostIP, txns, n.walletKey) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
contracts = append(contracts, api.Contract{ | ||
HostKey: c.HostKey(), | ||
HostIP: hostIP, | ||
ID: c.ID(), | ||
RenterKey: renterKey, | ||
}) | ||
} | ||
|
||
// upload | ||
data := frand.Bytes(20) | ||
slabs, err := c.UploadSlabs(bytes.NewReader(data), 2, 3, contracts) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
o := object.Object{ | ||
Slabs: make([]slab.SlabSlice, len(slabs)), | ||
} | ||
for i := range slabs { | ||
o.Slabs[i] = slab.SlabSlice{ | ||
Slab: slabs[i], | ||
Offset: 0, | ||
Length: uint32(len(data)), | ||
} | ||
} | ||
|
||
// store object | ||
if err := c.AddObject("foo", o); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// retrieve object | ||
o, err = c.Object("foo") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// download | ||
var buf bytes.Buffer | ||
if err := c.DownloadSlabs(&buf, o.Slabs, 0, o.Size(), contracts); err != nil { | ||
t.Fatal(err) | ||
} else if !bytes.Equal(buf.Bytes(), data) { | ||
t.Fatalf("data mismatch:\n%v (%v)\n%v (%v)", buf.Bytes(), len(buf.Bytes()), data, len(data)) | ||
} | ||
} |
Oops, something went wrong.