2019-01-11 03:02:26 +01:00
package manager
2017-12-28 18:14:33 +01:00
import (
2019-01-30 13:42:23 +01:00
"fmt"
"os"
"strconv"
2017-12-28 18:14:33 +01:00
"time"
2019-01-11 02:34:34 +01:00
"github.com/lbryio/lbry.go/extras/errors"
"github.com/lbryio/lbry.go/extras/jsonrpc"
2017-12-28 18:14:33 +01:00
"github.com/lbryio/lbry.go/lbrycrd"
"github.com/shopspring/decimal"
log "github.com/sirupsen/logrus"
)
func ( s * Sync ) walletSetup ( ) error {
2018-06-06 23:47:28 +02:00
//prevent unnecessary concurrent execution
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-01-30 13:42:23 +01:00
balance , err := strconv . ParseFloat ( ( string ) ( * balanceResp ) , 64 )
if err != nil {
return errors . Err ( err )
}
log . Debugf ( "Starting balance is %.4f" , balance )
2017-12-28 18:14:33 +01:00
2018-08-14 16:48:55 +02:00
var numOnSource int
2018-02-13 18:47:05 +01:00
if s . LbryChannelName == "@UCBerkeley" {
numOnSource = 10104
} else {
2018-08-14 16:48:55 +02:00
n , err := s . CountVideos ( )
2018-02-13 18:47:05 +01:00
if err != nil {
return err
}
2018-08-14 16:48:55 +02:00
numOnSource = int ( n )
2017-12-28 18:14:33 +01:00
}
2019-02-15 14:11:38 +01:00
2017-12-28 18:14:33 +01:00
log . Debugf ( "Source channel has %d videos" , numOnSource )
2018-06-15 23:03:28 +02:00
if numOnSource == 0 {
return nil
}
2017-12-28 18:14:33 +01:00
2018-08-23 00:28:31 +02:00
s . syncedVideosMux . RLock ( )
2018-08-14 16:48:55 +02:00
numPublished := len ( s . syncedVideos ) //should we only count published videos? Credits are allocated even for failed ones...
2018-08-23 00:28:31 +02:00
s . syncedVideosMux . RUnlock ( )
2018-09-20 01:05:47 +02:00
log . Debugf ( "We already allocated credits for %d videos" , numPublished )
2017-12-28 18:14:33 +01:00
2018-09-26 06:08:18 +02:00
if numOnSource - numPublished > s . Manager . videosLimit {
numOnSource = s . Manager . videosLimit
2018-07-24 02:01:35 +02:00
}
2018-07-31 01:19:12 +02:00
2018-05-26 02:43:16 +02:00
minBalance := ( float64 ( numOnSource ) - float64 ( numPublished ) ) * ( publishAmount + 0.1 ) + channelClaimAmount
2019-01-30 13:42:23 +01:00
if numPublished > numOnSource && balance < 1 {
2018-07-24 02:01:35 +02:00
SendErrorToSlack ( "something is going on as we published more videos than those available on source: %d/%d" , numPublished , numOnSource )
2018-05-07 22:26:46 +02:00
minBalance = 1 //since we ended up in this function it means some juice is still needed
}
2019-01-30 13:42:23 +01:00
amountToAdd := minBalance - balance
2018-03-12 21:58:37 +01:00
if s . Refill > 0 {
if amountToAdd < 0 {
amountToAdd = float64 ( s . Refill )
} else {
amountToAdd += float64 ( s . Refill )
}
}
2017-12-28 18:14:33 +01:00
if amountToAdd > 0 {
2018-01-09 16:49:21 +01:00
if amountToAdd < 1 {
2018-03-12 21:58:37 +01:00
amountToAdd = 1 // no reason to bother adding less than 1 credit
2018-01-09 16:49:21 +01:00
}
2019-01-30 13:42:23 +01:00
err := s . addCredits ( amountToAdd )
if err != nil {
return errors . Err ( err )
}
2017-12-28 18:14:33 +01:00
}
2019-01-30 13:42:23 +01:00
claimAddress , err := s . daemon . AddressList ( 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-01-30 13:42:23 +01:00
s . claimAddress = string ( ( * claimAddress ) [ 0 ] )
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
}
err = s . ensureEnoughUTXOs ( )
if err != nil {
return err
}
return nil
}
func ( s * Sync ) ensureEnoughUTXOs ( ) error {
2019-01-30 13:42:23 +01:00
accounts , err := s . daemon . AccountList ( )
if err != nil {
return errors . Err ( err )
}
accountsNet := ( * accounts ) . LBCMainnet
if os . Getenv ( "REGTEST" ) == "true" {
accountsNet = ( * accounts ) . LBCRegtest
}
defaultAccount := ""
for _ , account := range accountsNet {
if account . IsDefaultAccount {
defaultAccount = account . ID
break
}
}
if defaultAccount == "" {
return errors . Err ( "No default account found" )
}
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
for _ , utxo := range * utxolist {
2019-01-30 13:42:23 +01:00
amount , _ := strconv . ParseFloat ( utxo . Amount , 64 )
if ! utxo . IsClaim && ! utxo . IsSupport && ! utxo . IsUpdate && amount != 0.0 {
2017-12-28 18:14:33 +01:00
count ++
}
}
2019-01-30 13:42:23 +01:00
log . Infof ( "utxo count: %d" , count )
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-01-30 13:42:23 +01:00
balanceAmount , err := strconv . ParseFloat ( ( string ) ( * balance ) , 64 )
if err != nil {
return errors . Err ( err )
}
2019-02-15 14:11:38 +01:00
broadcastFee := 0.01
2019-01-30 13:42:23 +01:00
amountToSplit := fmt . Sprintf ( "%.6f" , balanceAmount - broadcastFee )
log . Infof ( "Splitting balance of %s evenly between 40 UTXOs" , * balance )
2017-12-28 18:14:33 +01:00
2019-01-30 13:42:23 +01:00
prefillTx , err := s . daemon . AccountFund ( defaultAccount , defaultAccount , amountToSplit , uint64 ( target ) )
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
}
2018-08-03 23:19:36 +02:00
err = s . waitForNewBlock ( )
2017-12-28 18:14:33 +01:00
if err != nil {
return err
}
2018-08-03 19:50:17 +02:00
} else if ! allUTXOsConfirmed ( utxolist ) {
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 {
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-02-15 14:11:38 +01:00
//@TODO: get rid of this when imported channels are supported
if s . YoutubeChannelID == "UCkK9UDm_ZNrq_rIXCz3xCGA" || s . YoutubeChannelID == "UCW-thz5HxE-goYq8yPds1Gw" {
return nil
}
2019-01-30 13:42:23 +01:00
channels , err := s . daemon . ChannelList ( nil , 1 , 50 )
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-01-03 17:01:00 +01:00
//special case for wallets we don't retain full control anymore
2019-01-30 13:42:23 +01:00
if len ( ( * channels ) . Items ) > 1 {
2019-01-03 17:01:00 +01:00
// This wallet is probably not under our control anymore but we still want to publish to it
// here we shall check if within all the channels there is one that was created by ytsync
SendInfoToSlack ( "we are dealing with a wallet that has multiple channels. This indicates that the wallet was probably transferred but we still want to sync their content. YoutubeID: %s" , s . YoutubeChannelID )
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-01-03 17:01:00 +01:00
if c . ClaimID != s . lbryChannelID {
if c . Name != s . LbryChannelName {
return errors . Err ( "the channel in the wallet is different than the channel in the database" )
}
return nil // we have the ytsync channel and both the claimID and the channelName from the database are correct
}
}
}
2019-01-30 13:42:23 +01:00
if len ( ( * channels ) . Items ) == 1 {
channel := ( ( * channels ) . Items ) [ 0 ]
2017-12-28 18:14:33 +01:00
if channel . Name == s . LbryChannelName {
2018-12-25 01:23:40 +01:00
//TODO: eventually get rid of this when the whole db is filled
if s . lbryChannelID == "" {
2018-12-28 16:28:01 +01:00
err = s . Manager . apiConfig . SetChannelClaimID ( s . YoutubeChannelID , channel . ClaimID )
2019-01-03 14:08:45 +01:00
} else if channel . ClaimID != s . lbryChannelID {
return errors . Err ( "the channel in the wallet is different than the channel in the database" )
}
2018-08-03 23:19:36 +02:00
s . lbryChannelID = channel . ClaimID
2019-01-03 17:01:00 +01:00
return err
2017-12-28 18:14:33 +01:00
} else {
2019-01-03 17:01:00 +01:00
return errors . Err ( "this channel does not belong to this wallet! Expected: %s, found: %s" , s . LbryChannelName , channel . Name )
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-01-30 13:42:23 +01:00
balance , err := decimal . NewFromString ( ( string ) ( * balanceResp ) )
if err != nil {
return errors . Err ( err )
}
2018-01-10 23:07:16 +01:00
if balance . LessThan ( decimal . NewFromFloat ( channelBidAmount ) ) {
2018-12-25 01:23:40 +01:00
err = s . addCredits ( channelBidAmount + 0.1 )
if err != nil {
return err
}
2018-01-10 23:07:16 +01:00
}
2019-01-30 13:42:23 +01:00
c , err := s . daemon . ChannelNew ( s . LbryChannelName , channelBidAmount , nil )
2017-12-28 18:14:33 +01:00
if err != nil {
return err
}
2018-08-03 23:19:36 +02:00
s . lbryChannelID = c . 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
}
func allUTXOsConfirmed ( utxolist * jsonrpc . UTXOListResponse ) bool {
if utxolist == nil {
return false
}
if len ( * utxolist ) < 1 {
return false
}
for _ , utxo := range * utxolist {
if utxo . Height == 0 {
return false
}
}
return true
}
2018-01-10 23:07:16 +01:00
func ( s * Sync ) addCredits ( amountToAdd float64 ) error {
log . Printf ( "Adding %f credits" , amountToAdd )
2018-08-08 23:59:59 +02:00
var lbrycrdd * lbrycrd . Client
var err error
if s . LbrycrdString == "" {
lbrycrdd , err = lbrycrd . NewWithDefaultURL ( )
if err != nil {
return err
}
} else {
lbrycrdd , err = lbrycrd . New ( 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
}