Skip to content

Commit

Permalink
Tonutils storage provider integration
Browse files Browse the repository at this point in the history
  • Loading branch information
xssnick committed Jan 24, 2024
1 parent 26b4bc8 commit 45d96b4
Show file tree
Hide file tree
Showing 22 changed files with 1,429 additions and 80 deletions.
30 changes: 30 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,36 @@ func (a *App) GetSpeedLimit() *api.SpeedLimits {
return limits
}

func (a *App) GetProviderContract(hash, owner string) api.ProviderContract {
return a.api.GetProviderContract(hash, owner)
}

func (a *App) FetchProviderRates(hash, provider string) api.ProviderRates {
return a.api.FetchProviderRates(hash, provider)
}

func (a *App) RequestProviderStorageInfo(hash, provider, owner string) api.ProviderStorageInfo {
return a.api.RequestProviderStorageInfo(hash, provider, owner)
}

func (a *App) BuildProviderContractData(hash, ownerAddr, amount string, providers []api.NewProviderData) *api.Transaction {
t, err := a.api.BuildProviderContractData(hash, ownerAddr, amount, providers)
if err != nil {
a.ShowWarnMsg(err.Error())
return nil
}
return t
}

func (a *App) BuildWithdrawalContractData(hash, ownerAddr string) *api.Transaction {
t, err := a.api.BuildWithdrawalContractData(hash, ownerAddr)
if err != nil {
a.ShowWarnMsg(err.Error())
return nil
}
return t
}

func (a *App) openFile(data []byte) {
if a.loaded {
res := a.addByMeta(data)
Expand Down
296 changes: 296 additions & 0 deletions core/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ package api

import (
"context"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"github.com/tonutils/torrent-client/core/client"
"github.com/xssnick/tonutils-go/address"
"github.com/xssnick/tonutils-go/tlb"
"github.com/xssnick/tonutils-storage-provider/pkg/contract"
"log"
"math/big"
"sort"
Expand All @@ -31,6 +36,12 @@ type PlainFile struct {
RawSize int64
}

type NewProviderData struct {
Key string
MaxSpan uint32
PricePerMBDay string
}

type Torrent struct {
ID string
Name string
Expand Down Expand Up @@ -84,6 +95,44 @@ type Speed struct {
Download string
}

type ProviderContract struct {
Success bool
Deployed bool
Address string
Providers []Provider
Balance string
}

type ProviderRates struct {
Success bool
Reason string
Provider Provider
}

type Provider struct {
Key string
LastProof string
PricePerDay string
Span string
Status string
Reason string
Progress float64
Data NewProviderData
}

type ProviderStorageInfo struct {
Status string
Reason string
Downloaded float64
}

type Transaction struct {
Body string
StateInit string
Address string
Amount string
}

type StorageClient interface {
GetTorrents(ctx context.Context) (*client.TorrentsList, error)
AddByHash(ctx context.Context, hash []byte, dir string) (*client.TorrentFull, error)
Expand All @@ -98,6 +147,11 @@ type StorageClient interface {
GetSpeedLimits(ctx context.Context) (*client.SpeedLimits, error)
SetSpeedLimits(ctx context.Context, download, upload int64) error
GetUploadStats(ctx context.Context, hash []byte) (uint64, error)
FetchProviderContract(ctx context.Context, torrentHash []byte, owner *address.Address) (*client.ProviderContractData, error)
FetchProviderRates(ctx context.Context, torrentHash, providerKey []byte) (*client.ProviderRates, error)
RequestProviderStorageInfo(ctx context.Context, torrentHash, providerKey []byte, owner *address.Address) (*client.ProviderStorageInfo, error)
BuildAddProviderTransaction(ctx context.Context, torrentHash []byte, owner *address.Address, providers []client.NewProviderData) (addr *address.Address, bodyData, stateInit []byte, err error)
BuildWithdrawalTransaction(torrentHash []byte, owner *address.Address) (addr *address.Address, bodyData []byte, err error)
}

type API struct {
Expand Down Expand Up @@ -639,6 +693,248 @@ func (a *API) SetPriorities(hash string, list []string, priority int) error {
return nil
}

func (a *API) GetProviderContract(hash, ownerAddr string) ProviderContract {
hashBytes, err := toHashBytes(hash)
if err != nil {
return ProviderContract{Success: false}
}

addr, err := address.ParseAddr(ownerAddr)
if err != nil {
log.Println("failed to get provider contract, parse addr error:", err.Error())
return ProviderContract{Success: false}
}

data, err := a.client.FetchProviderContract(a.globalCtx, hashBytes, addr)
if err != nil {
if errors.Is(err, contract.ErrNotDeployed) {
return ProviderContract{Success: true, Deployed: false}
}
log.Println("failed to get provider contract:", err.Error())
return ProviderContract{Success: false}
}

var providers []Provider
for _, p := range data.Providers {
since := "Never"
snc := time.Since(p.LastProofAt)
if snc < time.Minute {
since = fmt.Sprint(int(snc.Seconds())) + " seconds ago"
} else if snc < time.Hour {
since = fmt.Sprint(int(snc.Minutes())) + " minutes ago"
} else if snc < 24*time.Hour {
since = fmt.Sprint(int(snc.Hours())) + " hours ago"
} else if snc < 1000*24*time.Hour {
since = fmt.Sprint(int(snc.Hours())/24) + " days ago"
}

every := ""
if p.MaxSpan < 3600 {
every = fmt.Sprint(p.MaxSpan/60) + " Minutes"
} else if p.MaxSpan < 100*3600 {
every = fmt.Sprint(p.MaxSpan/3600) + " Hours"
} else {
every = fmt.Sprint(p.MaxSpan/86400) + " Days"
}

psi, err := a.client.RequestProviderStorageInfo(a.globalCtx, hashBytes, p.Key, addr)
if err != nil {
log.Println("failed to request provider info:", err.Error())
return ProviderContract{Success: false}
}

providers = append(providers, Provider{
Key: strings.ToUpper(hex.EncodeToString(p.Key)),
LastProof: since,
Span: every,
Progress: psi.Progress,
Status: psi.Status,
Reason: psi.Reason,
PricePerDay: tlb.FromNanoTON(new(big.Int).Mul(p.RatePerMB.Nano(), big.NewInt(int64(data.Size/1024/1024)))).String() + " TON",
Data: NewProviderData{
Key: hex.EncodeToString(p.Key),
MaxSpan: p.MaxSpan,
PricePerMBDay: p.RatePerMB.Nano().String(),
},
})

}

bal := data.Balance.String()
if idx := strings.IndexByte(bal, '.'); idx != -1 {
if len(bal) > idx+4 {
// max 4 digits after comma
bal = bal[:idx+4]
}
}
return ProviderContract{
Success: true,
Deployed: true,
Address: data.Address.String(),
Providers: providers,
Balance: bal + " TON",
}
}

func (a *API) FetchProviderRates(hash, provider string) ProviderRates {
hashBytes, err := toHashBytes(hash)
if err != nil {
return ProviderRates{Success: false, Reason: "failed to parse torrent hash: " + err.Error()}
}

providerBytes, err := toHashBytes(provider)
if err != nil {
return ProviderRates{Success: false, Reason: "failed to parse provider hash: " + err.Error()}
}

rates, err := a.client.FetchProviderRates(a.globalCtx, hashBytes, providerBytes)
if err != nil {
return ProviderRates{Success: false, Reason: err.Error()}
}

span := uint32(86400)
if span > rates.MaxSpan {
span = rates.MaxSpan
} else if span < rates.MinSpan {
span = rates.MinSpan
}

every := ""
if span < 3600 {
every = fmt.Sprint(span/60) + " minutes"
} else if span < 100*3600 {
every = fmt.Sprint(span/3600) + " hours"
} else {
every = fmt.Sprint(span/86400) + " days"
}

ratePerMB := rates.RatePerMBDay.Nano()
min := rates.MinBounty.Nano()
perDay := new(big.Int).Mul(ratePerMB, big.NewInt(int64(rates.Size/1024/1024)))
if perDay.Cmp(min) < 0 {
// increase reward to fit min bounty
coff := new(big.Float).Quo(new(big.Float).SetInt(min), new(big.Float).SetInt(perDay))
coff = coff.Add(coff, big.NewFloat(0.01)) // increase a bit to not be less than needed
ratePerMB, _ = new(big.Float).Mul(new(big.Float).SetInt(ratePerMB), coff).Int(ratePerMB)
perDay = new(big.Int).Mul(ratePerMB, big.NewInt(int64(rates.Size/1024/1024)))
}

return ProviderRates{
Success: true,
Provider: Provider{
Key: strings.ToUpper(hex.EncodeToString(providerBytes)),
PricePerDay: tlb.FromNanoTON(perDay).String() + " TON",
Span: every,
Data: NewProviderData{
Key: hex.EncodeToString(providerBytes),
MaxSpan: span,
PricePerMBDay: ratePerMB.String(),
},
},
}
}

func (a *API) RequestProviderStorageInfo(hash, provider, ownerAddr string) ProviderStorageInfo {
hashBytes, err := toHashBytes(hash)
if err != nil {
return ProviderStorageInfo{Status: "internal"}
}

providerBytes, err := toHashBytes(provider)
if err != nil {
return ProviderStorageInfo{Status: "internal"}
}

addr, err := address.ParseAddr(ownerAddr)
if err != nil {
log.Println("failed to get provider contract, parse addr error:", err.Error())
return ProviderStorageInfo{Status: "internal"}
}

info, err := a.client.RequestProviderStorageInfo(a.globalCtx, hashBytes, providerBytes, addr)
if err != nil {
return ProviderStorageInfo{Status: "not_connected"}
}

return ProviderStorageInfo{
Status: info.Status,
Reason: info.Reason,
Downloaded: info.Progress,
}
}

func (a *API) BuildProviderContractData(hash, ownerAddr, amount string, providers []NewProviderData) (*Transaction, error) {
hashBytes, err := toHashBytes(hash)
if err != nil {
return nil, err
}

owner, err := address.ParseAddr(ownerAddr)
if err != nil {
return nil, err
}

amt, err := tlb.FromTON(amount)
if err != nil {
return nil, err
}

var prs []client.NewProviderData
for _, p := range providers {
keyBytes, err := toHashBytes(p.Key)
if err != nil {
return nil, fmt.Errorf("provider key: %w", err)
}

price, ok := new(big.Int).SetString(p.PricePerMBDay, 10)
if !ok {
return nil, fmt.Errorf("incorrect amount format")
}

prs = append(prs, client.NewProviderData{
Address: address.NewAddress(0, 0, keyBytes),
MaxSpan: p.MaxSpan,
PricePerMBDay: tlb.FromNanoTON(price),
})
}

addr, body, si, err := a.client.BuildAddProviderTransaction(a.globalCtx, hashBytes, owner, prs)
if err != nil {
return nil, err
}

return &Transaction{
Body: base64.StdEncoding.EncodeToString(body),
StateInit: base64.StdEncoding.EncodeToString(si),
Address: addr.Bounce(false).String(),
Amount: amt.Nano().String(),
}, nil
}

func (a *API) BuildWithdrawalContractData(hash, ownerAddr string) (*Transaction, error) {
hashBytes, err := toHashBytes(hash)
if err != nil {
return nil, err
}

owner, err := address.ParseAddr(ownerAddr)
if err != nil {
return nil, err
}

addr, body, err := a.client.BuildWithdrawalTransaction(hashBytes, owner)
if err != nil {
return nil, err
}

return &Transaction{
Body: base64.StdEncoding.EncodeToString(body),
StateInit: "",
Address: addr.Bounce(true).String(),
Amount: tlb.MustFromTON("0.03").Nano().String(),
}, nil
}

func toHashBytes(hash string) ([]byte, error) {
hashBytes, err := hex.DecodeString(hash)
if err != nil {
Expand Down
Loading

0 comments on commit 45d96b4

Please sign in to comment.