2019-01-11 03:02:26 +01:00
package manager
2017-12-28 18:14:33 +01:00
import (
2019-06-12 03:35:21 +02:00
"fmt"
2019-06-10 01:41:52 +02:00
"math"
2019-04-19 03:22:51 +02:00
"net/http"
2019-01-30 13:42:23 +01:00
"strconv"
2017-12-28 18:14:33 +01:00
"time"
2019-10-10 16:50:33 +02:00
"github.com/lbryio/lbry.go/v2/extras/errors"
"github.com/lbryio/lbry.go/v2/extras/jsonrpc"
"github.com/lbryio/lbry.go/v2/extras/util"
2019-06-01 01:46:16 +02:00
"github.com/lbryio/ytsync/tagsManager"
2019-04-19 03:22:51 +02:00
"github.com/lbryio/ytsync/thumbs"
2019-07-15 22:16:02 +02:00
logUtils "github.com/lbryio/ytsync/util"
2017-12-28 18:14:33 +01:00
"github.com/shopspring/decimal"
log "github.com/sirupsen/logrus"
2019-04-19 03:22:51 +02:00
"google.golang.org/api/googleapi/transport"
"google.golang.org/api/youtube/v3"
2017-12-28 18:14:33 +01:00
)
2019-05-06 21:56:56 +02:00
func ( s * Sync ) enableAddressReuse ( ) error {
accountsResponse , err := s . daemon . AccountList ( )
if err != nil {
return errors . Err ( err )
}
accounts := accountsResponse . LBCMainnet
2019-08-04 00:34:48 +02:00
if logUtils . IsRegTest ( ) {
2019-05-06 21:56:56 +02:00
accounts = accountsResponse . LBCRegtest
}
for _ , a := range accounts {
_ , err = s . daemon . AccountSet ( a . ID , jsonrpc . AccountSettings {
2019-06-12 22:42:42 +02:00
ChangeMaxUses : util . PtrToInt ( 1000 ) ,
ReceivingMaxUses : util . PtrToInt ( 100 ) ,
2019-05-06 21:56:56 +02:00
} )
if err != nil {
return errors . Err ( err )
}
}
return nil
}
2017-12-28 18:14:33 +01:00
func ( s * Sync ) walletSetup ( ) error {
2019-06-12 03:17:59 +02:00
//prevent unnecessary concurrent execution and publishing while refilling/reallocating UTXOs
2018-08-14 16:48:55 +02:00
s . walletMux . Lock ( )
defer s . walletMux . Unlock ( )
2018-01-10 23:07:16 +01:00
err := s . ensureChannelOwnership ( )
if err != nil {
return err
}
2019-01-30 13:42:23 +01:00
balanceResp , err := s . daemon . AccountBalance ( nil )
2017-12-28 18:14:33 +01:00
if err != nil {
return err
} else if balanceResp == nil {
2018-03-09 17:47:38 +01:00
return errors . Err ( "no response" )
2017-12-28 18:14:33 +01:00
}
2019-08-21 20:01:24 +02:00
balance , err := strconv . ParseFloat ( balanceResp . Available . String ( ) , 64 )
2019-01-30 13:42:23 +01:00
if err != nil {
return errors . Err ( err )
}
log . Debugf ( "Starting balance is %.4f" , balance )
2017-12-28 18:14:33 +01:00
2019-05-06 21:56:56 +02:00
n , err := s . CountVideos ( )
if err != nil {
return err
2017-12-28 18:14:33 +01:00
}
2019-06-12 03:17:59 +02:00
videosOnYoutube := int ( n )
2019-02-15 14:11:38 +01:00
2019-06-12 03:17:59 +02:00
log . Debugf ( "Source channel has %d videos" , videosOnYoutube )
if videosOnYoutube == 0 {
2018-06-15 23:03:28 +02:00
return nil
}
2017-12-28 18:14:33 +01:00
2018-08-23 00:28:31 +02:00
s . syncedVideosMux . RLock ( )
2019-06-12 03:17:59 +02:00
publishedCount := 0
notUpgradedCount := 0
failedCount := 0
for _ , sv := range s . syncedVideos {
if sv . Published {
publishedCount ++
if sv . MetadataVersion < 2 {
notUpgradedCount ++
}
} else {
failedCount ++
}
}
2018-08-23 00:28:31 +02:00
s . syncedVideosMux . RUnlock ( )
2017-12-28 18:14:33 +01:00
2019-06-22 12:08:11 +02:00
log . Debugf ( "We already allocated credits for %d published videos and %d failed videos" , publishedCount , failedCount )
2018-07-31 01:19:12 +02:00
2019-06-12 03:17:59 +02:00
if videosOnYoutube > s . Manager . videosLimit {
videosOnYoutube = s . Manager . videosLimit
}
2019-06-22 12:08:11 +02:00
unallocatedVideos := videosOnYoutube - ( publishedCount + failedCount )
2019-09-04 19:24:11 +02:00
channelFee := channelClaimAmount
channelAlreadyClaimed := s . lbryChannelID != ""
if channelAlreadyClaimed {
channelFee = 0.0
}
requiredBalance := float64 ( unallocatedVideos ) * ( publishAmount + estimatedMaxTxFee ) + channelFee
2019-08-30 21:08:28 +02:00
if s . Manager . SyncFlags . UpgradeMetadata {
2019-06-12 03:17:59 +02:00
requiredBalance += float64 ( notUpgradedCount ) * 0.001
2019-05-24 19:01:16 +02:00
}
2019-06-12 03:17:59 +02:00
refillAmount := 0.0
if balance < requiredBalance || balance < minimumAccountBalance {
2019-09-04 19:24:11 +02:00
refillAmount = math . Max ( math . Max ( requiredBalance - balance , minimumAccountBalance - balance ) , minimumRefillAmount )
2018-05-07 22:26:46 +02:00
}
2018-03-12 21:58:37 +01:00
if s . Refill > 0 {
2019-06-12 03:17:59 +02:00
refillAmount += float64 ( s . Refill )
2018-03-12 21:58:37 +01:00
}
2017-12-28 18:14:33 +01:00
2019-06-12 03:17:59 +02:00
if refillAmount > 0 {
err := s . addCredits ( refillAmount )
2019-01-30 13:42:23 +01:00
if err != nil {
return errors . Err ( err )
}
2017-12-28 18:14:33 +01:00
}
2019-09-24 18:51:06 +02:00
claimAddress , err := s . daemon . AddressList ( nil , nil )
2017-12-28 18:14:33 +01:00
if err != nil {
return err
} else if claimAddress == nil {
2018-03-09 17:47:38 +01:00
return errors . Err ( "could not get unused address" )
2017-12-28 18:14:33 +01:00
}
2019-09-24 18:51:06 +02:00
s . claimAddress = string ( ( * claimAddress ) [ 0 ] . Address )
2017-12-28 18:14:33 +01:00
if s . claimAddress == "" {
2018-03-09 17:47:38 +01:00
return errors . Err ( "found blank claim address" )
2017-12-28 18:14:33 +01:00
}
2019-09-24 18:51:06 +02:00
if s . shouldTransfer ( ) {
2019-10-08 01:59:18 +02:00
s . claimAddress = s . clientPublishAddress
2019-08-16 05:34:25 +02:00
}
2017-12-28 18:14:33 +01:00
err = s . ensureEnoughUTXOs ( )
if err != nil {
return err
}
return nil
}
2019-10-16 19:38:45 +02:00
2019-10-01 16:55:43 +02:00
func ( s * Sync ) getDefaultAccount ( ) ( string , error ) {
2019-10-16 19:38:45 +02:00
if s . defaultAccountID == "" {
accounts , err := s . daemon . AccountList ( )
if err != nil {
return "" , errors . Err ( err )
}
accountsNet := ( * accounts ) . LBCMainnet
if logUtils . IsRegTest ( ) {
accountsNet = ( * accounts ) . LBCRegtest
}
for _ , account := range accountsNet {
if account . IsDefault {
s . defaultAccountID = account . ID
break
}
}
if s . defaultAccountID == "" {
return "" , errors . Err ( "No default account found" )
2019-01-30 13:42:23 +01:00
}
}
2019-10-16 19:38:45 +02:00
return s . defaultAccountID , nil
2019-10-01 16:55:43 +02:00
}
func ( s * Sync ) ensureEnoughUTXOs ( ) error {
defaultAccount , err := s . getDefaultAccount ( )
if err != nil {
return err
2019-01-30 13:42:23 +01:00
}
utxolist , err := s . daemon . UTXOList ( & defaultAccount )
2017-12-28 18:14:33 +01:00
if err != nil {
return err
} else if utxolist == nil {
2018-03-09 17:47:38 +01:00
return errors . Err ( "no response" )
2017-12-28 18:14:33 +01:00
}
2018-05-05 18:18:44 +02:00
target := 40
2018-08-10 02:59:52 +02:00
slack := int ( float32 ( 0.1 ) * float32 ( target ) )
2017-12-28 18:14:33 +01:00
count := 0
2019-06-26 02:45:27 +02:00
confirmedCount := 0
2017-12-28 18:14:33 +01:00
for _ , utxo := range * utxolist {
2019-01-30 13:42:23 +01:00
amount , _ := strconv . ParseFloat ( utxo . Amount , 64 )
2019-06-26 04:28:43 +02:00
if utxo . IsMine && utxo . Type == "payment" && amount > 0.001 {
2019-06-26 02:45:27 +02:00
if utxo . Confirmations > 0 {
confirmedCount ++
}
2017-12-28 18:14:33 +01:00
count ++
}
}
2019-06-26 02:45:27 +02:00
log . Infof ( "utxo count: %d (%d confirmed)" , count , confirmedCount )
UTXOWaitThreshold := 16
2018-08-08 23:59:59 +02:00
if count < target - slack {
2019-01-30 13:42:23 +01:00
balance , err := s . daemon . AccountBalance ( & defaultAccount )
2017-12-28 18:14:33 +01:00
if err != nil {
return err
} else if balance == nil {
2018-03-09 17:47:38 +01:00
return errors . Err ( "no response" )
2017-12-28 18:14:33 +01:00
}
2019-08-21 20:01:24 +02:00
balanceAmount , err := strconv . ParseFloat ( balance . Available . String ( ) , 64 )
2019-01-30 13:42:23 +01:00
if err != nil {
return errors . Err ( err )
}
2019-06-12 05:33:13 +02:00
maxUTXOs := uint64 ( 500 )
2019-06-12 03:17:59 +02:00
desiredUTXOCount := uint64 ( math . Floor ( ( balanceAmount ) / 0.1 ) )
2019-06-12 05:33:13 +02:00
if desiredUTXOCount > maxUTXOs {
desiredUTXOCount = maxUTXOs
}
2019-08-23 01:28:51 +02:00
availableBalance , _ := balance . Available . Float64 ( )
log . Infof ( "Splitting balance of %.3f evenly between %d UTXOs" , availableBalance , desiredUTXOCount )
2017-12-28 18:14:33 +01:00
2019-06-12 05:33:13 +02:00
broadcastFee := 0.1
prefillTx , err := s . daemon . AccountFund ( defaultAccount , defaultAccount , fmt . Sprintf ( "%.4f" , balanceAmount - broadcastFee ) , desiredUTXOCount , false )
2017-12-28 18:14:33 +01:00
if err != nil {
return err
} else if prefillTx == nil {
2018-03-09 17:47:38 +01:00
return errors . Err ( "no response" )
2017-12-28 18:14:33 +01:00
}
2019-06-26 02:45:27 +02:00
if confirmedCount < UTXOWaitThreshold {
err = s . waitForNewBlock ( )
if err != nil {
return err
}
2017-12-28 18:14:33 +01:00
}
2019-06-26 02:45:27 +02:00
} else if confirmedCount < UTXOWaitThreshold {
2018-08-03 19:50:17 +02:00
log . Println ( "Waiting for previous txns to confirm" )
2018-08-03 23:19:36 +02:00
err := s . waitForNewBlock ( )
2018-08-03 19:50:17 +02:00
if err != nil {
return err
}
2017-12-28 18:14:33 +01:00
}
return nil
}
2018-08-03 23:19:36 +02:00
func ( s * Sync ) waitForNewBlock ( ) error {
2019-08-01 04:42:22 +02:00
if logUtils . IsRegTest ( ) && logUtils . IsUsingDocker ( ) {
lbrycrd , err := logUtils . GetLbrycrdClient ( s . LbrycrdString )
if err != nil {
return errors . Prefix ( "error getting lbrycrd client: " , err )
}
txs , err := lbrycrd . Generate ( 1 )
for _ , tx := range txs {
log . Info ( "Generated tx: " , tx . String ( ) )
}
}
2018-08-03 23:19:36 +02:00
status , err := s . daemon . Status ( )
if err != nil {
return err
}
2018-08-10 02:19:37 +02:00
for status . Wallet . Blocks == 0 || status . Wallet . BlocksBehind != 0 {
2018-08-03 23:19:36 +02:00
time . Sleep ( 5 * time . Second )
status , err = s . daemon . Status ( )
2017-12-28 18:14:33 +01:00
if err != nil {
return err
}
2018-08-03 23:19:36 +02:00
}
2018-08-10 02:19:37 +02:00
currentBlock := status . Wallet . Blocks
for i := 0 ; status . Wallet . Blocks <= currentBlock ; i ++ {
2018-08-03 23:19:36 +02:00
if i % 3 == 0 {
log . Printf ( "Waiting for new block (%d)..." , currentBlock + 1 )
2017-12-28 18:14:33 +01:00
}
2018-08-03 23:19:36 +02:00
time . Sleep ( 10 * time . Second )
status , err = s . daemon . Status ( )
if err != nil {
return err
2018-05-05 16:15:02 +02:00
}
2017-12-28 18:14:33 +01:00
}
2018-08-03 23:19:36 +02:00
return nil
2017-12-28 18:14:33 +01:00
}
func ( s * Sync ) ensureChannelOwnership ( ) error {
if s . LbryChannelName == "" {
2018-03-09 17:47:38 +01:00
return errors . Err ( "no channel name set" )
2017-12-28 18:14:33 +01:00
}
2019-10-08 02:13:17 +02:00
2019-08-23 01:28:51 +02:00
if s . transferState == TransferStateComplete {
return nil
}
2019-10-10 16:50:33 +02:00
channels , err := s . daemon . ChannelList ( nil , 1 , 50 , nil )
2017-12-28 18:14:33 +01:00
if err != nil {
return err
} else if channels == nil {
2018-03-09 17:47:38 +01:00
return errors . Err ( "no channel response" )
2017-12-28 18:14:33 +01:00
}
2019-08-15 19:31:43 +02:00
var channelToUse * jsonrpc . Transaction
if len ( ( * channels ) . Items ) > 0 {
2019-01-03 17:01:00 +01:00
if s . lbryChannelID == "" {
return errors . Err ( "this channel does not have a recorded claimID in the database. To prevent failures, updates are not supported until an entry is manually added in the database" )
}
2019-01-30 13:42:23 +01:00
for _ , c := range ( * channels ) . Items {
2019-08-15 19:31:43 +02:00
log . Debugf ( "checking listed channel %s (%s)" , c . ClaimID , c . Name )
2019-01-03 17:01:00 +01:00
if c . ClaimID != s . lbryChannelID {
2019-08-15 19:31:43 +02:00
continue
}
if c . Name != s . LbryChannelName {
return errors . Err ( "the channel in the wallet is different than the channel in the database" )
2019-01-03 17:01:00 +01:00
}
2019-08-15 19:31:43 +02:00
channelToUse = & c
break
}
if channelToUse == nil {
return errors . Err ( "this wallet has channels but not a single one is ours! Expected claim_id: %s (%s)" , s . lbryChannelID , s . LbryChannelName )
2019-01-03 17:01:00 +01:00
}
}
2019-08-15 19:31:43 +02:00
2019-05-09 17:06:56 +02:00
channelUsesOldMetadata := false
2019-08-15 19:31:43 +02:00
if channelToUse != nil {
channelUsesOldMetadata = channelToUse . Value . GetThumbnail ( ) == nil
if ! channelUsesOldMetadata {
return nil
2017-12-28 18:14:33 +01:00
}
}
channelBidAmount := channelClaimAmount
2019-01-30 13:42:23 +01:00
balanceResp , err := s . daemon . AccountBalance ( nil )
2018-01-10 23:07:16 +01:00
if err != nil {
return err
} else if balanceResp == nil {
2018-03-09 17:47:38 +01:00
return errors . Err ( "no response" )
2018-01-10 23:07:16 +01:00
}
2019-08-21 20:01:24 +02:00
balance , err := decimal . NewFromString ( balanceResp . Available . String ( ) )
2019-01-30 13:42:23 +01:00
if err != nil {
return errors . Err ( err )
}
2018-01-10 23:07:16 +01:00
if balance . LessThan ( decimal . NewFromFloat ( channelBidAmount ) ) {
2019-10-10 16:50:33 +02:00
err = s . addCredits ( channelBidAmount + 0.3 )
2018-12-25 01:23:40 +01:00
if err != nil {
return err
}
2018-01-10 23:07:16 +01:00
}
2019-04-19 03:22:51 +02:00
client := & http . Client {
Transport : & transport . APIKey { Key : s . APIConfig . YoutubeAPIKey } ,
}
service , err := youtube . New ( client )
if err != nil {
return errors . Prefix ( "error creating YouTube service" , err )
}
2018-01-10 23:07:16 +01:00
2019-05-28 22:40:31 +02:00
response , err := service . Channels . List ( "snippet,brandingSettings" ) . Id ( s . YoutubeChannelID ) . Do ( )
2019-04-19 03:22:51 +02:00
if err != nil {
return errors . Prefix ( "error getting channel details" , err )
}
if len ( response . Items ) < 1 {
return errors . Err ( "youtube channel not found" )
}
channelInfo := response . Items [ 0 ] . Snippet
2019-05-24 19:01:16 +02:00
channelBranding := response . Items [ 0 ] . BrandingSettings
2019-04-19 03:22:51 +02:00
2019-05-03 05:11:52 +02:00
thumbnail := thumbs . GetBestThumbnail ( channelInfo . Thumbnails )
thumbnailURL , err := thumbs . MirrorThumbnail ( thumbnail . Url , s . YoutubeChannelID , s . Manager . GetS3AWSConfig ( ) )
2019-04-19 03:22:51 +02:00
if err != nil {
return err
}
2019-05-24 19:01:16 +02:00
2019-05-28 22:40:31 +02:00
var bannerURL * string
2019-05-24 19:01:16 +02:00
if channelBranding . Image != nil && channelBranding . Image . BannerImageUrl != "" {
bURL , err := thumbs . MirrorThumbnail ( channelBranding . Image . BannerImageUrl , "banner-" + s . YoutubeChannelID , s . Manager . GetS3AWSConfig ( ) )
if err != nil {
return err
}
bannerURL = & bURL
}
2019-04-19 03:22:51 +02:00
var languages [ ] string = nil
if channelInfo . DefaultLanguage != "" {
languages = [ ] string { channelInfo . DefaultLanguage }
}
var locations [ ] jsonrpc . Location = nil
if channelInfo . Country != "" {
locations = [ ] jsonrpc . Location { { Country : util . PtrToString ( channelInfo . Country ) } }
}
2019-05-09 17:06:56 +02:00
var c * jsonrpc . TransactionSummary
2019-06-04 22:21:40 +02:00
claimCreateOptions := jsonrpc . ClaimCreateOptions {
Title : & channelInfo . Title ,
Description : & channelInfo . Description ,
Tags : tagsManager . GetTagsForChannel ( s . YoutubeChannelID ) ,
Languages : languages ,
Locations : locations ,
ThumbnailURL : & thumbnailURL ,
}
2019-05-09 17:06:56 +02:00
if channelUsesOldMetadata {
c , err = s . daemon . ChannelUpdate ( s . lbryChannelID , jsonrpc . ChannelUpdateOptions {
2019-06-01 01:46:16 +02:00
ClearTags : util . PtrToBool ( true ) ,
2019-05-09 17:06:56 +02:00
ClearLocations : util . PtrToBool ( true ) ,
ClearLanguages : util . PtrToBool ( true ) ,
ChannelCreateOptions : jsonrpc . ChannelCreateOptions {
2019-06-04 22:21:40 +02:00
ClaimCreateOptions : claimCreateOptions ,
CoverURL : bannerURL ,
2019-05-09 17:06:56 +02:00
} ,
} )
} else {
c , err = s . daemon . ChannelCreate ( s . LbryChannelName , channelBidAmount , jsonrpc . ChannelCreateOptions {
2019-06-04 22:21:40 +02:00
ClaimCreateOptions : claimCreateOptions ,
CoverURL : bannerURL ,
2019-05-09 17:06:56 +02:00
} )
}
2017-12-28 18:14:33 +01:00
if err != nil {
return err
}
2019-04-19 03:22:51 +02:00
s . lbryChannelID = c . Outputs [ 0 ] . ClaimID
2018-12-25 01:23:40 +01:00
return s . Manager . apiConfig . SetChannelClaimID ( s . YoutubeChannelID , s . lbryChannelID )
2017-12-28 18:14:33 +01:00
}
2018-01-10 23:07:16 +01:00
func ( s * Sync ) addCredits ( amountToAdd float64 ) error {
log . Printf ( "Adding %f credits" , amountToAdd )
2019-08-01 04:42:22 +02:00
lbrycrdd , err := logUtils . GetLbrycrdClient ( s . LbrycrdString )
if err != nil {
return err
2018-01-10 23:07:16 +01:00
}
2019-01-30 13:42:23 +01:00
addressResp , err := s . daemon . AddressUnused ( nil )
2018-01-10 23:07:16 +01:00
if err != nil {
return err
} else if addressResp == nil {
2018-03-09 17:47:38 +01:00
return errors . Err ( "no response" )
2018-01-10 23:07:16 +01:00
}
address := string ( * addressResp )
_ , err = lbrycrdd . SimpleSend ( address , amountToAdd )
if err != nil {
return err
}
wait := 15 * time . Second
log . Println ( "Waiting " + wait . String ( ) + " for lbryum to let us know we have the new transaction" )
time . Sleep ( wait )
2018-06-15 23:03:28 +02:00
return nil
2018-01-10 23:07:16 +01:00
}