github.com/piotrnar/gocoin@v0.0.0-20240512203912-faa0448c5e96/client/usif/usif.go (about)

     1  package usif
     2  
     3  import (
     4  	"encoding/binary"
     5  	"encoding/hex"
     6  	"errors"
     7  	"fmt"
     8  	"math/rand"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/piotrnar/gocoin/client/common"
    13  	"github.com/piotrnar/gocoin/client/network"
    14  	"github.com/piotrnar/gocoin/client/network/peersdb"
    15  	"github.com/piotrnar/gocoin/lib/btc"
    16  	"github.com/piotrnar/gocoin/lib/others/qdb"
    17  	"github.com/piotrnar/gocoin/lib/others/sys"
    18  	"github.com/piotrnar/gocoin/lib/script"
    19  )
    20  
    21  type OneUiReq struct {
    22  	Param   string
    23  	Handler func(pars string)
    24  	Done    sync.WaitGroup
    25  }
    26  
    27  // A thread that wants to lock the main thread calls:
    28  // In.Add(1); Out.Add(1); [put msg into LocksChan]; In.Wait(); [do synchronized code]; Out.Done()
    29  // The main thread, upon receiving the message, does:
    30  // In.Done(); Out.Wait();
    31  type OneLock struct {
    32  	In  sync.WaitGroup // main thread calls Done() on this one and then Stop.Wait()
    33  	Out sync.WaitGroup // the synchronized thread calls Done
    34  }
    35  
    36  var (
    37  	UiChannel chan *OneUiReq = make(chan *OneUiReq, 1)
    38  	LocksChan chan *OneLock  = make(chan *OneLock, 1)
    39  
    40  	FetchingBalances sys.SyncBool
    41  	Exit_now         sys.SyncBool
    42  )
    43  
    44  func DecodeTxSops(tx *btc.Tx) (s string, missinginp bool, totinp, totout uint64, sigops uint, e error) {
    45  	s += fmt.Sprintln("Transaction details (for your information):")
    46  	s += fmt.Sprintln(len(tx.TxIn), "Input(s):")
    47  	sigops = btc.WITNESS_SCALE_FACTOR * tx.GetLegacySigOpCount()
    48  	tx.Spent_outputs = make([]*btc.TxOut, len(tx.TxIn))
    49  	ss := make([]string, len(tx.TxIn))
    50  	for i := range tx.TxIn {
    51  		ss[i] += fmt.Sprintf(" %3d %s seq=0x%x", i, tx.TxIn[i].Input.String(), tx.TxIn[i].Sequence)
    52  		var po *btc.TxOut
    53  
    54  		inpid := btc.NewUint256(tx.TxIn[i].Input.Hash[:])
    55  		if txinmem, ok := network.TransactionsToSend[inpid.BIdx()]; ok {
    56  			ss[i] += fmt.Sprint(" mempool")
    57  			if int(tx.TxIn[i].Input.Vout) >= len(txinmem.TxOut) {
    58  				ss[i] += fmt.Sprintf(" - Vout TOO BIG (%d/%d)!", int(tx.TxIn[i].Input.Vout), len(txinmem.TxOut))
    59  			} else {
    60  				po = txinmem.TxOut[tx.TxIn[i].Input.Vout]
    61  			}
    62  		} else {
    63  			po = common.BlockChain.Unspent.UnspentGet(&tx.TxIn[i].Input)
    64  			if po != nil {
    65  				ss[i] += fmt.Sprintf("%8d", po.BlockHeight)
    66  			}
    67  		}
    68  		tx.Spent_outputs[i] = po
    69  	}
    70  	for i := range tx.TxIn {
    71  		s += ss[i]
    72  		po := tx.Spent_outputs[i]
    73  		if po != nil {
    74  			ok := script.VerifyTxScript(po.Pk_script, &script.SigChecker{Amount: po.Value, Idx: i, Tx: tx}, script.VER_P2SH|script.VER_DERSIG|script.VER_CLTV)
    75  			if !ok {
    76  				s += fmt.Sprintln("\nERROR: The transacion does not have a valid signature.")
    77  				e = errors.New("Invalid signature")
    78  			}
    79  			totinp += po.Value
    80  
    81  			ads := "???"
    82  			if ad := btc.NewAddrFromPkScript(po.Pk_script, common.Testnet); ad != nil {
    83  				ads = ad.String()
    84  			}
    85  			s += fmt.Sprintf("\n\t%15.8f BTC @ %s", float64(po.Value)/1e8, ads)
    86  
    87  			if btc.IsP2SH(po.Pk_script) {
    88  				so := btc.WITNESS_SCALE_FACTOR * btc.GetP2SHSigOpCount(tx.TxIn[i].ScriptSig)
    89  				s += fmt.Sprintf("  + %d sigops", so)
    90  				sigops += so
    91  			}
    92  
    93  			swo := tx.CountWitnessSigOps(i, po.Pk_script)
    94  			if swo > 0 {
    95  				s += fmt.Sprintf("  + %d segops", swo)
    96  				sigops += swo
    97  			}
    98  
    99  			s += "\n"
   100  		} else {
   101  			s += fmt.Sprintln(" - UNKNOWN INPUT")
   102  			missinginp = true
   103  		}
   104  	}
   105  	s += fmt.Sprintln(len(tx.TxOut), "Output(s):")
   106  	for i := range tx.TxOut {
   107  		totout += tx.TxOut[i].Value
   108  		adr := btc.NewAddrFromPkScript(tx.TxOut[i].Pk_script, common.Testnet)
   109  		if adr != nil {
   110  			s += fmt.Sprintf(" %15.8f BTC to adr %s\n", float64(tx.TxOut[i].Value)/1e8, adr.String())
   111  		} else {
   112  			s += fmt.Sprintf(" %15.8f BTC to scr %s\n", float64(tx.TxOut[i].Value)/1e8, hex.EncodeToString(tx.TxOut[i].Pk_script))
   113  		}
   114  	}
   115  	if missinginp {
   116  		s += fmt.Sprintln("WARNING: There are missing inputs and we cannot calc input BTC amount.")
   117  		s += fmt.Sprintln("If there is somethign wrong with this transaction, you can loose money...")
   118  	} else {
   119  		s += fmt.Sprintf("All OK: %.8f BTC in -> %.8f BTC out, with %.8f BTC fee (%.2f SPB)\n", float64(totinp)/1e8,
   120  			float64(totout)/1e8, float64(totinp-totout)/1e8, float64(totinp-totout)/float64(tx.VSize()))
   121  	}
   122  
   123  	s += fmt.Sprintln("ECDSA sig operations : ", sigops)
   124  
   125  	return
   126  }
   127  
   128  func DecodeTx(tx *btc.Tx) (s string, missinginp bool, totinp, totout uint64, e error) {
   129  	s, missinginp, totinp, totout, _, e = DecodeTxSops(tx)
   130  	return
   131  }
   132  
   133  func LoadRawTx(buf []byte) (s string) {
   134  	txd, er := hex.DecodeString(string(buf))
   135  	if er != nil {
   136  		txd = buf
   137  	}
   138  
   139  	// At this place we should have raw transaction in txd
   140  	tx, le := btc.NewTx(txd)
   141  	if tx == nil || le != len(txd) {
   142  		s += fmt.Sprintln("Could not decode transaction file or it has some extra data")
   143  		return
   144  	}
   145  	tx.SetHash(txd)
   146  
   147  	s, _, _, _, _ = DecodeTx(tx)
   148  
   149  	network.RemoveFromRejected(&tx.Hash) // in case we rejected it eariler, to try it again as trusted
   150  
   151  	if why := network.NeedThisTxExt(&tx.Hash, nil); why != 0 {
   152  		s += fmt.Sprintln("Transaction not needed or not wanted", why)
   153  		network.TxMutex.Lock()
   154  		if t2s := network.TransactionsToSend[tx.Hash.BIdx()]; t2s != nil {
   155  			t2s.Local = true // make as own (if not needed)
   156  		}
   157  		network.TxMutex.Unlock()
   158  		return
   159  	}
   160  
   161  	if !network.SubmitLocalTx(tx, txd) {
   162  		network.TxMutex.Lock()
   163  		rr := network.TransactionsRejected[tx.Hash.BIdx()]
   164  		network.TxMutex.Unlock()
   165  		if rr != nil {
   166  			s += fmt.Sprintln("Transaction rejected", rr.Reason)
   167  		} else {
   168  			s += fmt.Sprintln("Transaction rejected in a weird way")
   169  		}
   170  		return
   171  	}
   172  
   173  	network.TxMutex.Lock()
   174  	_, ok := network.TransactionsToSend[tx.Hash.BIdx()]
   175  	network.TxMutex.Unlock()
   176  	if ok {
   177  		s += fmt.Sprintln("Transaction added to the memory pool. You can broadcast it now.")
   178  	} else {
   179  		s += fmt.Sprintln("Transaction not rejected, but also not accepted - very strange!")
   180  	}
   181  
   182  	return
   183  }
   184  
   185  func SendInvToRandomPeer(typ uint32, h *btc.Uint256) {
   186  	common.CountSafe(fmt.Sprint("NetSendOneInv", typ))
   187  
   188  	// Prepare the inv
   189  	inv := new([36]byte)
   190  	binary.LittleEndian.PutUint32(inv[0:4], typ)
   191  	copy(inv[4:36], h.Bytes())
   192  
   193  	// Append it to PendingInvs in a random connection
   194  	network.Mutex_net.Lock()
   195  	idx := rand.Intn(len(network.OpenCons))
   196  	var cnt int
   197  	for _, v := range network.OpenCons {
   198  		if idx == cnt {
   199  			v.Mutex.Lock()
   200  			v.PendingInvs = append(v.PendingInvs, inv)
   201  			v.Mutex.Unlock()
   202  			break
   203  		}
   204  		cnt++
   205  	}
   206  	network.Mutex_net.Unlock()
   207  	return
   208  }
   209  
   210  func GetNetworkHashRateNum() float64 {
   211  	hours := common.CFG.Stat.HashrateHrs
   212  	common.Last.Mutex.Lock()
   213  	end := common.Last.Block
   214  	common.Last.Mutex.Unlock()
   215  	now := time.Now().Unix()
   216  	cnt := 0
   217  	var diff float64
   218  	for ; end != nil; cnt++ {
   219  		if now-int64(end.Timestamp()) > int64(hours)*3600 {
   220  			break
   221  		}
   222  		diff += btc.GetDifficulty(end.Bits())
   223  		end = end.Parent
   224  	}
   225  	if cnt == 0 {
   226  		return 0
   227  	}
   228  	diff /= float64(cnt)
   229  	bph := float64(cnt) / float64(hours)
   230  	return bph / 6 * diff * 7158278.826667
   231  }
   232  
   233  func ExecUiReq(req *OneUiReq) {
   234  	if FetchingBalances.Get() {
   235  		fmt.Println("Client is currently busy fetching wallet balance.\nYour command has been queued and will execute soon.")
   236  	}
   237  
   238  	//fmt.Println("main.go last seen in line", common.BusyIn())
   239  	sta := time.Now().UnixNano()
   240  	req.Done.Add(1)
   241  	UiChannel <- req
   242  	go func() {
   243  		req.Done.Wait()
   244  		sto := time.Now().UnixNano()
   245  		fmt.Printf("Ready in %.3fs\n", float64(sto-sta)/1e9)
   246  		fmt.Print("> ")
   247  	}()
   248  }
   249  
   250  func MemoryPoolFees() (res string) {
   251  	res = fmt.Sprintln("Content of mempool sorted by fee's SPB:")
   252  	network.TxMutex.Lock()
   253  	defer network.TxMutex.Unlock()
   254  
   255  	sorted := network.GetSortedMempoolNew()
   256  
   257  	var totlen, rawlen uint64
   258  	for cnt := 0; cnt < len(sorted); cnt++ {
   259  		v := sorted[cnt]
   260  		newlen := totlen + uint64(v.VSize())
   261  		rawlen += uint64(len(v.Raw))
   262  
   263  		if cnt == 0 || cnt+1 == len(sorted) || (newlen/100e3) != (totlen/100e3) {
   264  			spb := float64(v.Fee) / float64(v.VSize())
   265  			toprint := newlen
   266  			if cnt != 0 && cnt+1 != len(sorted) {
   267  				toprint = newlen / 100e3 * 100e3
   268  			}
   269  			res += fmt.Sprintf(" %9d / %9d bytes, %6d txs @ fee %8.1f Satoshis / byte\n", toprint, rawlen, cnt+1, spb)
   270  		}
   271  		if (newlen / 1e6) != (totlen / 1e6) {
   272  			res += "===========================================================\n"
   273  		}
   274  
   275  		totlen = newlen
   276  	}
   277  	return
   278  }
   279  
   280  // UnbanPeer unbans peer of a given IP or "all" banned peers
   281  func UnbanPeer(par string) (s string) {
   282  	var ad *peersdb.PeerAddr
   283  
   284  	if par != "all" {
   285  		var er error
   286  		ad, er = peersdb.NewAddrFromString(par, false)
   287  		if er != nil {
   288  			s = fmt.Sprintln(par, er.Error())
   289  			return
   290  		}
   291  		s += fmt.Sprintln("Unban", ad.Ip(), "...")
   292  		network.HammeringMutex.Lock()
   293  		delete(network.RecentlyDisconencted, ad.Ip4)
   294  		network.HammeringMutex.Unlock()
   295  	} else {
   296  		s += fmt.Sprintln("Unban all peers ...")
   297  		network.HammeringMutex.Lock()
   298  		network.RecentlyDisconencted = make(map[[4]byte]*network.RecentlyDisconenctedType)
   299  		network.HammeringMutex.Unlock()
   300  	}
   301  
   302  	var keys []qdb.KeyType
   303  	var vals [][]byte
   304  	peersdb.PeerDB.Browse(func(k qdb.KeyType, v []byte) uint32 {
   305  		peer := peersdb.NewPeer(v)
   306  		if peer.Banned != 0 {
   307  			if ad == nil || peer.Ip() == ad.Ip() {
   308  				s += fmt.Sprintln(" -", peer.NetAddr.String())
   309  				peer.Banned = 0
   310  				keys = append(keys, k)
   311  				vals = append(vals, peer.Bytes())
   312  			}
   313  		}
   314  		return 0
   315  	})
   316  	for i := range keys {
   317  		peersdb.PeerDB.Put(keys[i], vals[i])
   318  	}
   319  
   320  	s += fmt.Sprintln(len(keys), "peer(s) un-baned")
   321  	return
   322  }
   323  
   324  func GetReceivedBlockX(block *btc.Block) (rb *network.OneReceivedBlock, cbasetx *btc.Tx) {
   325  	network.MutexRcv.Lock()
   326  	rb = network.ReceivedBlocks[block.Hash.BIdx()]
   327  	if rb.TheWeight == 0 {
   328  		block.BuildTxListExt(false)
   329  		rb.NonWitnessSize = block.NoWitnessSize
   330  		rb.TheWeight = block.BlockWeight
   331  		rb.ThePaidVSize = block.PaidTxsVSize
   332  		rb.TheOrdCnt = block.OrbTxCnt
   333  		rb.TheOrdSize = block.OrbTxSize
   334  		rb.TheOrdWeight = block.OrbTxWeight
   335  		cbasetx = block.Txs[0]
   336  	} else {
   337  		cbasetx, _ = btc.NewTx(block.Raw[block.TxOffset:])
   338  	}
   339  	network.MutexRcv.Unlock()
   340  	return
   341  }
   342  
   343  func init() {
   344  	rand.Seed(int64(time.Now().Nanosecond()))
   345  }