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 }