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

     1  package webui
     2  
     3  import (
     4  	"archive/zip"
     5  	"bytes"
     6  	"encoding/hex"
     7  	"encoding/json"
     8  	"fmt"
     9  	"html"
    10  	"io/ioutil"
    11  	"net/http"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  
    16  	"github.com/piotrnar/gocoin/client/common"
    17  	"github.com/piotrnar/gocoin/client/network"
    18  	"github.com/piotrnar/gocoin/client/usif"
    19  	"github.com/piotrnar/gocoin/client/wallet"
    20  	"github.com/piotrnar/gocoin/lib/btc"
    21  	"github.com/piotrnar/gocoin/lib/utxo"
    22  )
    23  
    24  const (
    25  	AT_P2PKH       = "P2PKH"
    26  	AT_P2SH        = "P2SH"
    27  	AT_P2WSH       = "P2WSH"
    28  	AT_P2SH_P2WPKH = "P2SH-P2WPKH"
    29  	AT_P2WPKH      = "P2WPKH"
    30  	AT_P2TAP       = "P2TAP"
    31  )
    32  
    33  func p_wal(w http.ResponseWriter, r *http.Request) {
    34  	if !ipchecker(r) {
    35  		return
    36  	}
    37  
    38  	if !common.GetBool(&common.WalletON) {
    39  		p_wallet_is_off(w, r)
    40  		return
    41  	}
    42  
    43  	var str string
    44  	common.Last.Mutex.Lock()
    45  	if common.BlockChain.Consensus.Enforce_SEGWIT != 0 &&
    46  		common.Last.Block.Height >= common.BlockChain.Consensus.Enforce_SEGWIT {
    47  		str = "var segwit_active=true"
    48  	} else {
    49  		str = "var segwit_active=false"
    50  	}
    51  	common.Last.Mutex.Unlock()
    52  	page := load_template("wallet.html")
    53  	page = strings.Replace(page, "/*WALLET_JS_VARS*/", str, 1)
    54  	write_html_head(w, r)
    55  	w.Write([]byte(page))
    56  	write_html_tail(w)
    57  }
    58  
    59  func getaddrtype(aa *btc.BtcAddr) string {
    60  	if aa.SegwitProg != nil {
    61  		if aa.SegwitProg.Version == 0 && len(aa.SegwitProg.Program) == 20 {
    62  			return AT_P2WPKH
    63  		}
    64  		if aa.SegwitProg.Version == 1 && len(aa.SegwitProg.Program) == 32 {
    65  			return AT_P2TAP
    66  		}
    67  	}
    68  	if aa.Version == btc.AddrVerPubkey(common.Testnet) {
    69  		return AT_P2PKH
    70  	}
    71  	if aa.Version == btc.AddrVerScript(common.Testnet) {
    72  		return "P2SH"
    73  	}
    74  	return "unknown"
    75  }
    76  
    77  func json_balance(w http.ResponseWriter, r *http.Request) {
    78  	if !ipchecker(r) || !common.GetBool(&common.WalletON) {
    79  		return
    80  	}
    81  
    82  	if r.Method != "POST" {
    83  		return
    84  	}
    85  
    86  	summary := len(r.Form["summary"]) > 0
    87  	mempool := len(r.Form["mempool"]) > 0
    88  	getrawtx := len(r.Form["rawtx"]) > 0
    89  
    90  	inp, er := ioutil.ReadAll(r.Body)
    91  	if er != nil {
    92  		println(er.Error())
    93  		return
    94  	}
    95  
    96  	var addrs []string
    97  	er = json.Unmarshal(inp, &addrs)
    98  	if er != nil {
    99  		println(er.Error())
   100  		return
   101  	}
   102  
   103  	type OneOut struct {
   104  		TxId     string
   105  		Vout     uint32
   106  		Value    uint64
   107  		Height   uint32
   108  		Coinbase bool
   109  		Message  string
   110  		Addr     string
   111  		AddrType string
   112  		Spending bool   // if true the spending tx is in the mempool
   113  		RawTx    string `json:",omitempty"`
   114  	}
   115  
   116  	type OneOuts struct {
   117  		Value            uint64
   118  		OutCnt           int
   119  		SegWitCnt        int
   120  		SegWitAddr       string
   121  		SegWitNativeCnt  int
   122  		SegWitNativeAddr string
   123  		SegWitTapCnt     int
   124  		SegWitTapAddr    string
   125  		Outs             []OneOut
   126  
   127  		PendingCnt   int
   128  		PendingValue uint64
   129  		PendingOuts  []OneOut
   130  
   131  		SpendingValue uint64
   132  		SpendingCnt   uint64
   133  	}
   134  
   135  	out := make(map[string]*OneOuts)
   136  
   137  	lck := new(usif.OneLock)
   138  	lck.In.Add(1)
   139  	lck.Out.Add(1)
   140  	usif.LocksChan <- lck
   141  	lck.In.Wait()
   142  
   143  	var addr_map map[string]string
   144  
   145  	if mempool {
   146  		// make addrs -> idx
   147  		addr_map = make(map[string]string, 2*len(addrs))
   148  	}
   149  
   150  	for _, a := range addrs {
   151  		var aa, ab *btc.BtcAddr
   152  		var e error
   153  		var pubkey []byte
   154  		var as string
   155  
   156  		if len(a) == 66 && a[0] == '0' && (a[1] == '2' || a[1] == '3') {
   157  			pubkey, e = hex.DecodeString(a) // raw public key
   158  			if e != nil || len(pubkey) != 33 {
   159  				continue
   160  			}
   161  			if aa = btc.NewAddrFromPubkey(pubkey, btc.AddrVerPubkey(common.Testnet)); aa == nil {
   162  				continue
   163  			}
   164  		} else {
   165  			// bitcoin address (of some sort)
   166  			if aa, e = btc.NewAddrFromString(a); e != nil {
   167  				continue
   168  			}
   169  		}
   170  
   171  		unsp := wallet.GetAllUnspent(aa)
   172  		newrec := new(OneOuts)
   173  		if len(unsp) > 0 {
   174  			newrec.OutCnt = len(unsp)
   175  			as = aa.String()
   176  			for _, u := range unsp {
   177  				newrec.Value += u.Value
   178  				network.TxMutex.Lock()
   179  				_, spending := network.SpentOutputs[u.TxPrevOut.UIdx()]
   180  				network.TxMutex.Unlock()
   181  				if spending {
   182  					newrec.SpendingValue += u.Value
   183  					newrec.SpendingCnt++
   184  				}
   185  				if !summary {
   186  					txid := btc.NewUint256(u.TxPrevOut.Hash[:])
   187  					var rawtx string
   188  					if getrawtx {
   189  						dat, er := common.GetRawTx(uint32(u.MinedAt), txid)
   190  						if er == nil {
   191  							rawtx = hex.EncodeToString(dat)
   192  						}
   193  					}
   194  					newrec.Outs = append(newrec.Outs, OneOut{
   195  						TxId: btc.NewUint256(u.TxPrevOut.Hash[:]).String(), Vout: u.Vout,
   196  						Value: u.Value, Height: u.MinedAt, Coinbase: u.Coinbase,
   197  						Message: html.EscapeString(string(u.Message)), Addr: as, Spending: spending,
   198  						RawTx: rawtx, AddrType: getaddrtype(aa)})
   199  				}
   200  			}
   201  		}
   202  
   203  		out[a] = newrec
   204  
   205  		if mempool {
   206  			addr_map[string(aa.OutScript())] = a
   207  		}
   208  
   209  		/* For P2KH addr, we also check its segwit's P2SH-P2WPKH and Native P2WPKH */
   210  		if aa.SegwitProg == nil && aa.Version == btc.AddrVerPubkey(common.Testnet) {
   211  			p2kh := aa.Hash160
   212  
   213  			// P2SH SegWit if applicable
   214  			h160 := btc.Rimp160AfterSha256(append([]byte{0, 20}, p2kh[:]...))
   215  			ab = btc.NewAddrFromHash160(h160[:], btc.AddrVerScript(common.Testnet))
   216  			as = ab.String()
   217  			newrec.SegWitAddr = as
   218  			unsp = wallet.GetAllUnspent(ab)
   219  			if len(unsp) > 0 {
   220  				newrec.OutCnt += len(unsp)
   221  				newrec.SegWitCnt = len(unsp)
   222  				for _, u := range unsp {
   223  					newrec.Value += u.Value
   224  					network.TxMutex.Lock()
   225  					_, spending := network.SpentOutputs[u.TxPrevOut.UIdx()]
   226  					network.TxMutex.Unlock()
   227  					if spending {
   228  						newrec.SpendingValue += u.Value
   229  						newrec.SpendingCnt++
   230  					}
   231  					if !summary {
   232  						txid := btc.NewUint256(u.TxPrevOut.Hash[:])
   233  						var rawtx string
   234  						if getrawtx {
   235  							dat, er := common.GetRawTx(uint32(u.MinedAt), txid)
   236  							if er == nil {
   237  								rawtx = hex.EncodeToString(dat)
   238  							}
   239  						}
   240  						newrec.Outs = append(newrec.Outs, OneOut{
   241  							TxId: txid.String(), Vout: u.Vout,
   242  							Value: u.Value, Height: u.MinedAt, Coinbase: u.Coinbase,
   243  							Message: html.EscapeString(string(u.Message)), Addr: as,
   244  							Spending: spending, RawTx: rawtx, AddrType: AT_P2SH_P2WPKH})
   245  					}
   246  				}
   247  			}
   248  			if mempool {
   249  				addr_map[string(ab.OutScript())] = a
   250  			}
   251  
   252  			// Native SegWit if applicable
   253  			ab = btc.NewAddrFromPkScript(append([]byte{0, 20}, p2kh[:]...), common.Testnet)
   254  			as = ab.String()
   255  			newrec.SegWitNativeAddr = as
   256  			unsp = wallet.GetAllUnspent(ab)
   257  			if len(unsp) > 0 {
   258  				newrec.OutCnt += len(unsp)
   259  				newrec.SegWitNativeCnt = len(unsp)
   260  				for _, u := range unsp {
   261  					newrec.Value += u.Value
   262  					network.TxMutex.Lock()
   263  					_, spending := network.SpentOutputs[u.TxPrevOut.UIdx()]
   264  					network.TxMutex.Unlock()
   265  					if spending {
   266  						newrec.SpendingValue += u.Value
   267  						newrec.SpendingCnt++
   268  					}
   269  					if !summary {
   270  						txid := btc.NewUint256(u.TxPrevOut.Hash[:])
   271  						var rawtx string
   272  						if getrawtx {
   273  							dat, er := common.GetRawTx(uint32(u.MinedAt), txid)
   274  							if er == nil {
   275  								rawtx = hex.EncodeToString(dat)
   276  							}
   277  						}
   278  						newrec.Outs = append(newrec.Outs, OneOut{
   279  							TxId: txid.String(), Vout: u.Vout,
   280  							Value: u.Value, Height: u.MinedAt, Coinbase: u.Coinbase,
   281  							Message: html.EscapeString(string(u.Message)), Addr: as,
   282  							Spending: spending, RawTx: rawtx, AddrType: AT_P2WPKH})
   283  					}
   284  				}
   285  			}
   286  			if mempool {
   287  				addr_map[string(ab.OutScript())] = a
   288  			}
   289  
   290  			// Also Check PAY2TAP, if pubkey mode...
   291  			if pubkey != nil {
   292  				if ab = btc.NewAddrFromPubkey(pubkey, btc.AddrVerPubkey(common.Testnet)); ab == nil {
   293  					continue
   294  				}
   295  				ab.SegwitProg = &btc.SegwitProg{HRP: btc.GetSegwitHRP(common.Testnet), Version: 1, Program: pubkey[1:]}
   296  				as = ab.String()
   297  				newrec.SegWitTapAddr = as
   298  				unsp = wallet.GetAllUnspent(ab)
   299  				if len(unsp) > 0 {
   300  					newrec.OutCnt += len(unsp)
   301  					newrec.SegWitTapCnt = len(unsp)
   302  					for _, u := range unsp {
   303  						newrec.Value += u.Value
   304  						network.TxMutex.Lock()
   305  						_, spending := network.SpentOutputs[u.TxPrevOut.UIdx()]
   306  						network.TxMutex.Unlock()
   307  						if spending {
   308  							newrec.SpendingValue += u.Value
   309  							newrec.SpendingCnt++
   310  						}
   311  						if !summary {
   312  							txid := btc.NewUint256(u.TxPrevOut.Hash[:])
   313  							var rawtx string
   314  							if getrawtx {
   315  								dat, er := common.GetRawTx(uint32(u.MinedAt), txid)
   316  								if er == nil {
   317  									rawtx = hex.EncodeToString(dat)
   318  								}
   319  							}
   320  							newrec.Outs = append(newrec.Outs, OneOut{
   321  								TxId: txid.String(), Vout: u.Vout,
   322  								Value: u.Value, Height: u.MinedAt, Coinbase: u.Coinbase,
   323  								Message: html.EscapeString(string(u.Message)), Addr: as,
   324  								Spending: spending, RawTx: rawtx, AddrType: AT_P2TAP})
   325  						}
   326  					}
   327  				}
   328  				if mempool {
   329  					addr_map[string(ab.OutScript())] = a
   330  				}
   331  			}
   332  		}
   333  	}
   334  
   335  	// check memory pool
   336  	if mempool {
   337  		network.TxMutex.Lock()
   338  		for _, t2s := range network.TransactionsToSend {
   339  			for vo, to := range t2s.TxOut {
   340  				if a, ok := addr_map[string(to.Pk_script)]; ok {
   341  					var tpo btc.TxPrevOut
   342  					tpo.Hash = t2s.Hash.Hash
   343  					tpo.Vout = uint32(vo)
   344  					newrec := out[a]
   345  					newrec.PendingValue += to.Value
   346  					newrec.PendingCnt++
   347  					_, spending := network.SpentOutputs[tpo.UIdx()]
   348  					if spending {
   349  						newrec.SpendingValue += to.Value
   350  						newrec.SpendingCnt++
   351  					}
   352  					if !summary {
   353  						po := &btc.TxPrevOut{Hash: t2s.Hash.Hash, Vout: uint32(vo)}
   354  						_, spending := network.SpentOutputs[po.UIdx()]
   355  						newrec.PendingOuts = append(newrec.PendingOuts, OneOut{
   356  							TxId: t2s.Hash.String(), Vout: uint32(vo),
   357  							Value: to.Value, Spending: spending})
   358  					}
   359  				}
   360  			}
   361  		}
   362  		network.TxMutex.Unlock()
   363  	}
   364  
   365  	lck.Out.Done()
   366  
   367  	bx, er := json.Marshal(out)
   368  	if er == nil {
   369  		w.Header()["Content-Type"] = []string{"application/json"}
   370  		w.Write(bx)
   371  	} else {
   372  		println(er.Error())
   373  	}
   374  }
   375  
   376  func dl_balance(w http.ResponseWriter, r *http.Request) {
   377  	if !ipchecker(r) || !common.GetBool(&common.WalletON) {
   378  		return
   379  	}
   380  
   381  	if r.Method != "POST" {
   382  		return
   383  	}
   384  
   385  	var addrs []string
   386  	var labels []string
   387  
   388  	if len(r.Form["addrcnt"]) != 1 {
   389  		println("no addrcnt")
   390  		return
   391  	}
   392  	addrcnt, _ := strconv.ParseUint(r.Form["addrcnt"][0], 10, 32)
   393  
   394  	for i := 0; i < int(addrcnt); i++ {
   395  		is := fmt.Sprint(i)
   396  		if len(r.Form["addr"+is]) == 1 {
   397  			addrs = append(addrs, r.Form["addr"+is][0])
   398  			if len(r.Form["label"+is]) == 1 {
   399  				labels = append(labels, r.Form["label"+is][0])
   400  			} else {
   401  				labels = append(labels, "")
   402  			}
   403  		}
   404  	}
   405  
   406  	var thisbal utxo.AllUnspentTx
   407  
   408  	lck := new(usif.OneLock)
   409  	lck.In.Add(1)
   410  	lck.Out.Add(1)
   411  	usif.LocksChan <- lck
   412  	lck.In.Wait()
   413  
   414  	for idx, a := range addrs {
   415  		if aa, e := btc.NewAddrFromString(a); e == nil {
   416  			aa.Extra.Label = labels[idx]
   417  			newrecs := wallet.GetAllUnspent(aa)
   418  			if len(newrecs) > 0 {
   419  				thisbal = append(thisbal, newrecs...)
   420  			}
   421  
   422  			/* Segwit P2WPKH: */
   423  			if aa.SegwitProg == nil && aa.Version == btc.AddrVerPubkey(common.Testnet) {
   424  				p2kh := aa.Hash160
   425  
   426  				// P2SH SegWit if applicable
   427  				h160 := btc.Rimp160AfterSha256(append([]byte{0, 20}, aa.Hash160[:]...))
   428  				aa = btc.NewAddrFromHash160(h160[:], btc.AddrVerScript(common.Testnet))
   429  				newrecs = wallet.GetAllUnspent(aa)
   430  				if len(newrecs) > 0 {
   431  					thisbal = append(thisbal, newrecs...)
   432  				}
   433  
   434  				// Native SegWit if applicable
   435  				aa = btc.NewAddrFromPkScript(append([]byte{0, 20}, p2kh[:]...), common.Testnet)
   436  				newrecs = wallet.GetAllUnspent(aa)
   437  				if len(newrecs) > 0 {
   438  					thisbal = append(thisbal, newrecs...)
   439  				}
   440  			}
   441  		}
   442  	}
   443  	lck.Out.Done()
   444  
   445  	buf := new(bytes.Buffer)
   446  	zi := zip.NewWriter(buf)
   447  	was_tx := make(map[[32]byte]bool)
   448  
   449  	sort.Sort(thisbal)
   450  	for i := range thisbal {
   451  		if was_tx[thisbal[i].TxPrevOut.Hash] {
   452  			continue
   453  		}
   454  		was_tx[thisbal[i].TxPrevOut.Hash] = true
   455  		txid := btc.NewUint256(thisbal[i].TxPrevOut.Hash[:])
   456  		fz, _ := zi.Create("balance/" + txid.String() + ".tx")
   457  		if dat, er := common.GetRawTx(thisbal[i].MinedAt, txid); er == nil {
   458  			fz.Write(dat)
   459  		} else {
   460  			println(er.Error())
   461  		}
   462  	}
   463  
   464  	fz, _ := zi.Create("balance/unspent.txt")
   465  	for i := range thisbal {
   466  		fmt.Fprintln(fz, thisbal[i].UnspentTextLine())
   467  	}
   468  
   469  	zi.Close()
   470  	w.Header()["Content-Type"] = []string{"application/zip"}
   471  	w.Write(buf.Bytes())
   472  
   473  }
   474  
   475  func json_wallet_status(w http.ResponseWriter, r *http.Request) {
   476  	if !ipchecker(r) {
   477  		return
   478  	}
   479  
   480  	var out struct {
   481  		WalletON       bool
   482  		WalletProgress uint32
   483  		WalletOnIn     uint32
   484  	}
   485  	common.LockCfg()
   486  	out.WalletON = common.WalletON
   487  	out.WalletProgress = common.WalletProgress
   488  	out.WalletOnIn = common.WalletOnIn
   489  	common.UnlockCfg()
   490  
   491  	bx, er := json.Marshal(out)
   492  	if er == nil {
   493  		w.Header()["Content-Type"] = []string{"application/json"}
   494  		w.Write(bx)
   495  	} else {
   496  		println(er.Error())
   497  	}
   498  }