Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
lukechampine committed Aug 8, 2022
0 parents commit 47856c8
Show file tree
Hide file tree
Showing 51 changed files with 12,651 additions and 0 deletions.
21 changes: 21 additions & 0 deletions LICENSE
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.
5 changes: 5 additions & 0 deletions README.md
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.
135 changes: 135 additions & 0 deletions api/api.go
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"`
}
216 changes: 216 additions & 0 deletions api/api_test.go
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))
}
}
Loading

0 comments on commit 47856c8

Please sign in to comment.