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

     1  package wallet
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"fmt"
     7  	"sync"
     8  
     9  	"github.com/piotrnar/gocoin/client/common"
    10  	"github.com/piotrnar/gocoin/lib/btc"
    11  	"github.com/piotrnar/gocoin/lib/script"
    12  	"github.com/piotrnar/gocoin/lib/utxo"
    13  )
    14  
    15  var (
    16  	AllBalancesP2KH, AllBalancesP2SH, AllBalancesP2WKH map[[20]byte]*OneAllAddrBal
    17  	AllBalancesP2WSH, AllBalancesP2TAP                 map[[32]byte]*OneAllAddrBal
    18  	AccessMutex                                        sync.Mutex
    19  )
    20  
    21  type OneAllAddrInp [utxo.UtxoIdxLen + 4]byte
    22  
    23  type OneAllAddrBal struct {
    24  	Value   uint64 // Highest bit of it means P2SH
    25  	unsp    []OneAllAddrInp
    26  	unspMap map[OneAllAddrInp]bool
    27  }
    28  
    29  func (ur *OneAllAddrInp) GetRec() (rec *utxo.UtxoRec, vout uint32) {
    30  	var ind utxo.UtxoKeyType
    31  	copy(ind[:], ur[:])
    32  	common.BlockChain.Unspent.MapMutex[int(ind[0])].RLock()
    33  	v := common.BlockChain.Unspent.HashMap[int(ind[0])][ind]
    34  	common.BlockChain.Unspent.MapMutex[int(ind[0])].RUnlock()
    35  	if v != nil {
    36  		vout = binary.LittleEndian.Uint32(ur[utxo.UtxoIdxLen:])
    37  		rec = utxo.NewUtxoRec(ind, v)
    38  	}
    39  	return
    40  }
    41  
    42  func NewUTXO(tx *utxo.UtxoRec) {
    43  	var uidx [20]byte
    44  	var rec *OneAllAddrBal
    45  	var nr OneAllAddrInp
    46  
    47  	copy(nr[:utxo.UtxoIdxLen], tx.TxID[:]) //RecIdx
    48  
    49  	for vout := uint32(0); vout < uint32(len(tx.Outs)); vout++ {
    50  		out := tx.Outs[vout]
    51  		if out == nil {
    52  			continue
    53  		}
    54  		if out.Value < common.AllBalMinVal() {
    55  			continue
    56  		}
    57  		if script.IsP2KH(out.PKScr) {
    58  			copy(uidx[:], out.PKScr[3:23])
    59  			rec = AllBalancesP2KH[uidx]
    60  			if rec == nil {
    61  				rec = &OneAllAddrBal{}
    62  				AllBalancesP2KH[uidx] = rec
    63  			}
    64  		} else if script.IsP2SH(out.PKScr) {
    65  			copy(uidx[:], out.PKScr[2:22])
    66  			rec = AllBalancesP2SH[uidx]
    67  			if rec == nil {
    68  				rec = &OneAllAddrBal{}
    69  				AllBalancesP2SH[uidx] = rec
    70  			}
    71  		} else if script.IsP2WPKH(out.PKScr) {
    72  			copy(uidx[:], out.PKScr[2:22])
    73  			rec = AllBalancesP2WKH[uidx]
    74  			if rec == nil {
    75  				rec = &OneAllAddrBal{}
    76  				AllBalancesP2WKH[uidx] = rec
    77  			}
    78  		} else if script.IsP2WSH(out.PKScr) {
    79  			var uidx [32]byte
    80  			copy(uidx[:], out.PKScr[2:34])
    81  			rec = AllBalancesP2WSH[uidx]
    82  			if rec == nil {
    83  				rec = &OneAllAddrBal{}
    84  				AllBalancesP2WSH[uidx] = rec
    85  			}
    86  		} else if script.IsP2TAP(out.PKScr) {
    87  			var uidx [32]byte
    88  			copy(uidx[:], out.PKScr[2:34])
    89  			rec = AllBalancesP2TAP[uidx]
    90  			if rec == nil {
    91  				rec = &OneAllAddrBal{}
    92  				AllBalancesP2TAP[uidx] = rec
    93  			}
    94  		} else {
    95  			continue
    96  		}
    97  
    98  		binary.LittleEndian.PutUint32(nr[utxo.UtxoIdxLen:], vout)
    99  
   100  		rec.Value += out.Value
   101  
   102  		if rec.unspMap != nil {
   103  			rec.unspMap[nr] = true
   104  			continue
   105  		}
   106  		if len(rec.unsp) >= common.CFG.AllBalances.UseMapCnt-1 {
   107  			// Switch to using map
   108  			rec.unspMap = make(map[OneAllAddrInp]bool, 2*common.CFG.AllBalances.UseMapCnt)
   109  			for _, v := range rec.unsp {
   110  				rec.unspMap[v] = true
   111  			}
   112  			rec.unsp = nil
   113  			rec.unspMap[nr] = true
   114  			continue
   115  		}
   116  
   117  		rec.unsp = append(rec.unsp, nr)
   118  	}
   119  }
   120  
   121  func all_del_utxos(tx *utxo.UtxoRec, outs []bool) {
   122  	var uidx [20]byte
   123  	var uidx32 [32]byte
   124  	var rec *OneAllAddrBal
   125  	var i int
   126  	var nr OneAllAddrInp
   127  	var typ int                            // 0 - P2KH, 1 - P2SH, 2 - P2WKH
   128  	copy(nr[:utxo.UtxoIdxLen], tx.TxID[:]) //RecIdx
   129  	for vout := uint32(0); vout < uint32(len(tx.Outs)); vout++ {
   130  		if !outs[vout] {
   131  			continue
   132  		}
   133  		out := tx.Outs[vout]
   134  		if out == nil {
   135  			continue
   136  		}
   137  		if out.Value < common.AllBalMinVal() {
   138  			continue
   139  		}
   140  		if script.IsP2KH(out.PKScr) {
   141  			typ = 0
   142  			copy(uidx[:], out.PKScr[3:23])
   143  			rec = AllBalancesP2KH[uidx]
   144  		} else if script.IsP2SH(out.PKScr) {
   145  			typ = 1
   146  			copy(uidx[:], out.PKScr[2:22])
   147  			rec = AllBalancesP2SH[uidx]
   148  		} else if script.IsP2WPKH(out.PKScr) {
   149  			typ = 2
   150  			copy(uidx[:], out.PKScr[2:22])
   151  			rec = AllBalancesP2WKH[uidx]
   152  		} else if script.IsP2WSH(out.PKScr) {
   153  			typ = 3
   154  			copy(uidx32[:], out.PKScr[2:34])
   155  			rec = AllBalancesP2WSH[uidx32]
   156  		} else if script.IsP2TAP(out.PKScr) {
   157  			typ = 4
   158  			copy(uidx32[:], out.PKScr[2:34])
   159  			rec = AllBalancesP2TAP[uidx32]
   160  		} else {
   161  			continue
   162  		}
   163  
   164  		if rec == nil {
   165  			println("balance rec not found for", btc.NewAddrFromPkScript(out.PKScr, common.CFG.Testnet).String(),
   166  				btc.NewUint256(tx.TxID[:]).String(), vout, btc.UintToBtc(out.Value))
   167  			continue
   168  		}
   169  
   170  		binary.LittleEndian.PutUint32(nr[utxo.UtxoIdxLen:], vout)
   171  
   172  		if rec.unspMap != nil {
   173  			if _, ok := rec.unspMap[nr]; !ok {
   174  				println("unspent rec not in map for", btc.NewAddrFromPkScript(out.PKScr, common.CFG.Testnet).String())
   175  				continue
   176  			}
   177  			delete(rec.unspMap, nr)
   178  			if len(rec.unspMap) == 0 {
   179  				switch typ {
   180  				case 0:
   181  					delete(AllBalancesP2KH, uidx)
   182  				case 1:
   183  					delete(AllBalancesP2SH, uidx)
   184  				case 2:
   185  					delete(AllBalancesP2WKH, uidx)
   186  				case 3:
   187  					delete(AllBalancesP2WSH, uidx32)
   188  				case 4:
   189  					delete(AllBalancesP2TAP, uidx32)
   190  				}
   191  			} else {
   192  				rec.Value -= out.Value
   193  			}
   194  			continue
   195  		}
   196  
   197  		for i = 0; i < len(rec.unsp); i++ {
   198  			if bytes.Equal(rec.unsp[i][:], nr[:]) {
   199  				break
   200  			}
   201  		}
   202  		if i == len(rec.unsp) {
   203  			println("unspent rec not in list for", btc.NewAddrFromPkScript(out.PKScr, common.CFG.Testnet).String())
   204  			continue
   205  		}
   206  		if len(rec.unsp) == 1 {
   207  			switch typ {
   208  			case 0:
   209  				delete(AllBalancesP2KH, uidx)
   210  			case 1:
   211  				delete(AllBalancesP2SH, uidx)
   212  			case 2:
   213  				delete(AllBalancesP2WKH, uidx)
   214  			case 3:
   215  				delete(AllBalancesP2WSH, uidx32)
   216  			case 4:
   217  				delete(AllBalancesP2TAP, uidx32)
   218  			}
   219  		} else {
   220  			rec.Value -= out.Value
   221  			rec.unsp = append(rec.unsp[:i], rec.unsp[i+1:]...)
   222  		}
   223  	}
   224  }
   225  
   226  // TxNotifyAdd is called while accepting the block (from the chain's thread).
   227  func TxNotifyAdd(tx *utxo.UtxoRec) {
   228  	AccessMutex.Lock()
   229  	NewUTXO(tx)
   230  	AccessMutex.Unlock()
   231  }
   232  
   233  // TxNotifyDel is called while accepting the block (from the chain's thread).
   234  func TxNotifyDel(tx *utxo.UtxoRec, outs []bool) {
   235  	AccessMutex.Lock()
   236  	all_del_utxos(tx, outs)
   237  	AccessMutex.Unlock()
   238  }
   239  
   240  // Call the cb function for each unspent record
   241  func (r *OneAllAddrBal) Browse(cb func(*OneAllAddrInp)) {
   242  	if r.unspMap != nil {
   243  		for v := range r.unspMap {
   244  			cb(&v)
   245  		}
   246  	} else {
   247  		for _, v := range r.unsp {
   248  			cb(&v)
   249  		}
   250  	}
   251  }
   252  
   253  func (r *OneAllAddrBal) Count() int {
   254  	if r.unspMap != nil {
   255  		return len(r.unspMap)
   256  	} else {
   257  		return len(r.unsp)
   258  	}
   259  }
   260  
   261  func GetAllUnspent(aa *btc.BtcAddr) (thisbal utxo.AllUnspentTx) {
   262  	var rec *OneAllAddrBal
   263  	if aa.SegwitProg != nil {
   264  		var uidx [32]byte
   265  		if aa.SegwitProg.Version == 1 || len(aa.SegwitProg.Program) == 32 {
   266  			copy(uidx[:], aa.SegwitProg.Program)
   267  			rec = AllBalancesP2TAP[uidx]
   268  		} else {
   269  			if aa.SegwitProg.Version != 0 {
   270  				return
   271  			}
   272  			switch len(aa.SegwitProg.Program) {
   273  			case 20:
   274  				copy(aa.Hash160[:], aa.SegwitProg.Program)
   275  				rec = AllBalancesP2WKH[aa.Hash160]
   276  			case 32:
   277  				copy(uidx[:], aa.SegwitProg.Program)
   278  				rec = AllBalancesP2WSH[uidx]
   279  			default:
   280  				return
   281  			}
   282  		}
   283  	} else if aa.Version == btc.AddrVerPubkey(common.Testnet) {
   284  		rec = AllBalancesP2KH[aa.Hash160]
   285  	} else if aa.Version == btc.AddrVerScript(common.Testnet) {
   286  		rec = AllBalancesP2SH[aa.Hash160]
   287  	} else {
   288  		return
   289  	}
   290  	if rec != nil {
   291  		rec.Browse(func(v *OneAllAddrInp) {
   292  			if qr, vout := v.GetRec(); qr != nil {
   293  				if oo := qr.Outs[vout]; oo != nil {
   294  					unsp := &utxo.OneUnspentTx{TxPrevOut: btc.TxPrevOut{Hash: qr.TxID, Vout: vout},
   295  						Value: oo.Value, MinedAt: qr.InBlock, Coinbase: qr.Coinbase, BtcAddr: aa}
   296  
   297  					if int(vout+1) < len(qr.Outs) {
   298  						var msg []byte
   299  						if qr.Outs[vout+1] != nil && len(qr.Outs[vout+1].PKScr) > 1 && qr.Outs[vout+1].PKScr[0] == 0x6a {
   300  							msg = qr.Outs[vout+1].PKScr[1:]
   301  						} else if int(vout+1) != len(qr.Outs) && qr.Outs[len(qr.Outs)-1] != nil &&
   302  							len(qr.Outs[len(qr.Outs)-1].PKScr) > 1 && qr.Outs[len(qr.Outs)-1].PKScr[0] == 0x6a {
   303  							msg = qr.Outs[len(qr.Outs)-1].PKScr[1:]
   304  						}
   305  						if msg != nil {
   306  							_, unsp.Message, _, _ = btc.GetOpcode(msg)
   307  						}
   308  					}
   309  					thisbal = append(thisbal, unsp)
   310  				}
   311  			}
   312  		})
   313  	}
   314  	return
   315  }
   316  
   317  func PrintStat() {
   318  	var p2kh_maps, p2kh_outs, p2kh_vals uint64
   319  	for _, r := range AllBalancesP2KH {
   320  		p2kh_vals += r.Value
   321  		if r.unspMap != nil {
   322  			p2kh_maps++
   323  			p2kh_outs += uint64(len(r.unspMap))
   324  		} else {
   325  			p2kh_outs += uint64(len(r.unsp))
   326  		}
   327  	}
   328  
   329  	var p2sh_maps, p2sh_outs, p2sh_vals uint64
   330  	for _, r := range AllBalancesP2SH {
   331  		p2sh_vals += r.Value
   332  		if r.unspMap != nil {
   333  			p2sh_maps++
   334  			p2sh_outs += uint64(len(r.unspMap))
   335  		} else {
   336  			p2sh_outs += uint64(len(r.unsp))
   337  		}
   338  	}
   339  
   340  	var p2wkh_maps, p2wkh_outs, p2wkh_vals uint64
   341  	for _, r := range AllBalancesP2WKH {
   342  		p2wkh_vals += r.Value
   343  		if r.unspMap != nil {
   344  			p2wkh_maps++
   345  			p2wkh_outs += uint64(len(r.unspMap))
   346  		} else {
   347  			p2wkh_outs += uint64(len(r.unsp))
   348  		}
   349  	}
   350  
   351  	var p2wsh_maps, p2wsh_outs, p2wsh_vals uint64
   352  	for _, r := range AllBalancesP2WSH {
   353  		p2wsh_vals += r.Value
   354  		if r.unspMap != nil {
   355  			p2wsh_maps++
   356  			p2wsh_outs += uint64(len(r.unspMap))
   357  		} else {
   358  			p2wsh_outs += uint64(len(r.unsp))
   359  		}
   360  	}
   361  
   362  	var p2tap_maps, p2tap_outs, p2tap_vals uint64
   363  	for _, r := range AllBalancesP2TAP {
   364  		p2tap_vals += r.Value
   365  		if r.unspMap != nil {
   366  			p2tap_maps++
   367  			p2tap_outs += uint64(len(r.unspMap))
   368  		} else {
   369  			p2tap_outs += uint64(len(r.unsp))
   370  		}
   371  	}
   372  
   373  	fmt.Println("AllBalMinVal:", btc.UintToBtc(common.AllBalMinVal()), "  UseMapCnt:", common.CFG.AllBalances.UseMapCnt)
   374  
   375  	fmt.Println("AllBalancesP2KH: ", len(AllBalancesP2KH), "records,",
   376  		p2kh_outs, "outputs,", btc.UintToBtc(p2kh_vals), "BTC,", p2kh_maps, "maps")
   377  
   378  	fmt.Println("AllBalancesP2SH: ", len(AllBalancesP2SH), "records,",
   379  		p2sh_outs, "outputs,", btc.UintToBtc(p2sh_vals), "BTC,", p2sh_maps, "maps")
   380  
   381  	fmt.Println("AllBalancesP2WKH: ", len(AllBalancesP2WKH), "records,",
   382  		p2wkh_outs, "outputs,", btc.UintToBtc(p2wkh_vals), "BTC,", p2wkh_maps, "maps")
   383  
   384  	fmt.Println("AllBalancesP2WSH: ", len(AllBalancesP2WSH), "records,",
   385  		p2wsh_outs, "outputs,", btc.UintToBtc(p2wsh_vals), "BTC,", p2wsh_maps, "maps")
   386  
   387  	fmt.Println("AllBalancesP2TAP: ", len(AllBalancesP2TAP), "records,",
   388  		p2tap_outs, "outputs,", btc.UintToBtc(p2tap_vals), "BTC,", p2tap_maps, "maps")
   389  }