github.com/deroproject/derosuite@v2.1.6-1.0.20200307070847-0f2e589c7a2b+incompatible/blockchain/mempool/mempool.go (about) 1 // Copyright 2017-2018 DERO Project. All rights reserved. 2 // Use of this source code in any form is governed by RESEARCH license. 3 // license can be found in the LICENSE file. 4 // GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 5 // 6 // 7 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 8 // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 9 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 10 // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 11 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 12 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 13 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 14 // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 15 // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 16 17 package mempool 18 19 import "os" 20 import "fmt" 21 import "sync" 22 import "sort" 23 import "time" 24 import "sync/atomic" 25 import "path/filepath" 26 import "encoding/hex" 27 import "encoding/json" 28 29 import "github.com/romana/rlog" 30 import log "github.com/sirupsen/logrus" 31 32 import "github.com/deroproject/derosuite/transaction" 33 import "github.com/deroproject/derosuite/globals" 34 import "github.com/deroproject/derosuite/crypto" 35 36 // this is only used for sorting and nothing else 37 type TX_Sorting_struct struct { 38 FeesPerByte uint64 // this is fees per byte 39 Hash crypto.Hash // transaction hash 40 Size uint64 // transaction size 41 } 42 43 // NOTE: do NOT consider this code as useless, as it is used to avooid double spending attacks within the block and within the pool 44 // let me explain, since we are a state machine, we add block to our blockchain 45 // so, if a double spending attack comes, 2 transactions with same inputs, we reject one of them 46 // the algo is documented somewhere else which explains the entire process 47 48 // at this point in time, this is an ultrafast written mempool, 49 // it will not scale for more than 10000 transactions but is good enough for now 50 // we can always come back and rewrite it 51 // NOTE: the pool is now persistant 52 type Mempool struct { 53 txs sync.Map //map[crypto.Hash]*mempool_object 54 key_images sync.Map //map[crypto.Hash]bool // contains key images of all txs 55 sorted_by_fee []crypto.Hash // contains txids sorted by fees 56 sorted []TX_Sorting_struct // contains TX sorting information, so as new block can be forged easily 57 modified bool // used to monitor whethel mem pool contents have changed, 58 height uint64 // track blockchain height 59 60 P2P_TX_Relayer p2p_TX_Relayer // actual pointer, setup by the dero daemon during runtime 61 62 // global variable , but don't see it utilisation here except fot tx verification 63 //chain *Blockchain 64 Exit_Mutex chan bool 65 66 sync.Mutex 67 } 68 69 // this object is serialized and deserialized 70 type mempool_object struct { 71 Tx *transaction.Transaction 72 Added uint64 // time in epoch format 73 Height uint64 // at which height the tx unlocks in the mempool 74 Relayed int // relayed count 75 RelayedAt int64 // when was tx last relayed 76 Size uint64 // size in bytes of the TX 77 FEEperBYTE uint64 // fee per byte 78 } 79 80 var loggerpool *log.Entry 81 82 // marshal object as json 83 func (obj *mempool_object) MarshalJSON() ([]byte, error) { 84 return json.Marshal(&struct { 85 Tx string `json:"tx"` // hex encoding 86 Added uint64 `json:"added"` 87 Height uint64 `json:"height"` 88 Relayed int `json:"relayed"` 89 RelayedAt int64 `json:"relayedat"` 90 }{ 91 Tx: hex.EncodeToString(obj.Tx.Serialize()), 92 Added: obj.Added, 93 Height: obj.Height, 94 Relayed: obj.Relayed, 95 RelayedAt: obj.RelayedAt, 96 }) 97 } 98 99 // unmarshal object from json encoding 100 func (obj *mempool_object) UnmarshalJSON(data []byte) error { 101 aux := &struct { 102 Tx string `json:"tx"` 103 Added uint64 `json:"added"` 104 Height uint64 `json:"height"` 105 Relayed int `json:"relayed"` 106 RelayedAt int64 `json:"relayedat"` 107 }{} 108 109 if err := json.Unmarshal(data, &aux); err != nil { 110 return err 111 } 112 113 obj.Added = aux.Added 114 obj.Height = aux.Height 115 obj.Relayed = aux.Relayed 116 obj.RelayedAt = aux.RelayedAt 117 118 tx_bytes, err := hex.DecodeString(aux.Tx) 119 if err != nil { 120 return err 121 } 122 obj.Size = uint64(len(tx_bytes)) 123 124 obj.Tx = &transaction.Transaction{} 125 err = obj.Tx.DeserializeHeader(tx_bytes) 126 127 if err == nil { 128 obj.FEEperBYTE = obj.Tx.RctSignature.Get_TX_Fee() / obj.Size 129 } 130 return err 131 } 132 133 func Init_Mempool(params map[string]interface{}) (*Mempool, error) { 134 var mempool Mempool 135 //mempool.chain = params["chain"].(*Blockchain) 136 137 loggerpool = globals.Logger.WithFields(log.Fields{"com": "POOL"}) // all components must use this logger 138 loggerpool.Infof("Mempool started") 139 atomic.AddUint32(&globals.Subsystem_Active, 1) // increment subsystem 140 141 mempool.Exit_Mutex = make(chan bool) 142 143 // initialize maps 144 //mempool.txs = map[crypto.Hash]*mempool_object{} 145 //mempool.key_images = map[crypto.Hash]bool{} 146 147 //TODO load any trasactions saved at previous exit 148 149 mempool_file := filepath.Join(globals.GetDataDirectory(), "mempool.json") 150 151 file, err := os.Open(mempool_file) 152 if err != nil { 153 loggerpool.Warnf("Error opening mempool data file %s err %s", mempool_file, err) 154 } else { 155 defer file.Close() 156 157 var objects []mempool_object 158 decoder := json.NewDecoder(file) 159 err = decoder.Decode(&objects) 160 if err != nil { 161 loggerpool.Warnf("Error unmarshalling mempool data err %s", err) 162 } else { // successfully unmarshalled data, add it to mempool 163 loggerpool.Debugf("Will try to load %d txs from mempool file", (len(objects))) 164 for i := range objects { 165 result := mempool.Mempool_Add_TX(objects[i].Tx, 0) 166 if result { // setup time 167 //mempool.txs[objects[i].Tx.GetHash()] = &objects[i] // setup time and other artifacts 168 mempool.txs.Store(objects[i].Tx.GetHash(),&objects[i] ) 169 } 170 } 171 } 172 } 173 174 go mempool.Relayer_and_Cleaner() 175 176 return &mempool, nil 177 } 178 179 // this is created per incoming block and then discarded 180 // This does not require shutting down and will be garbage collected automatically 181 func Init_Block_Mempool(params map[string]interface{}) (*Mempool, error) { 182 var mempool Mempool 183 184 // initialize maps 185 //mempool.txs = map[crypto.Hash]*mempool_object{} 186 //mempool.key_images = map[crypto.Hash]bool{} 187 188 return &mempool, nil 189 } 190 191 func (pool *Mempool) HouseKeeping(height uint64, Verifier func(*transaction.Transaction) bool) { 192 pool.height = height 193 194 // this code is executed in rare conditions which are as follows 195 // chain has a tx which has spent most recent input possible (10 block) 196 // chain height = say 1000 , tx consumes outputs from 9990 197 // then a major soft fork occurs roughly at say 9997 198 // under this case of reorganisation, tx would have failed, since are still not mature 199 // so what we do is, when we push tx during reorganisation,we verify and make them available at point when they were mined 200 // thereby avoiding problem of input maturity 201 // carry over book keeping activities, such as keep transactions hidden till specific height is reached 202 // this is done to avoid a soft-fork issue, related to input maturity 203 var delete_list []crypto.Hash 204 205 pool.txs.Range(func(k, value interface{}) bool { 206 txhash := k.(crypto.Hash) 207 v := value.(*mempool_object) 208 209 if height > v.Height { // verify all tx in pool for double spending 210 if !Verifier(v.Tx) { // this tx could not verified against specific height 211 rlog.Warnf("!!MEMPOOL Deleting TX since atleast one of the key images is already consumed txid=%s chain height %d tx height %d", txhash, height, v.Height) 212 delete_list = append(delete_list, txhash) 213 } 214 } 215 216 // delete transactions whose keyimages have been mined already 217 if v.Height < height { 218 if !Verifier(v.Tx) { // this tx is already spent and thus can never be mined 219 delete_list = append(delete_list, txhash) 220 } 221 } 222 // expire TX which could not be mined in 1 day 86400 hrs 223 if v.Height == 0 && (v.Added+86400) <= uint64(time.Now().UTC().Unix()) { 224 rlog.Warnf("!! MEMPOOL Deleting TX since TX could not be mined in 1 day txid=%s chain height %d tx height %d", txhash, height, v.Height) 225 delete_list = append(delete_list, txhash) 226 } 227 // give 7 days time to alt chain txs 228 if v.Height != 0 && (v.Added+(86400*7)) <= uint64(time.Now().UTC().Unix()) { 229 rlog.Warnf("!! MEMPOOL Deleting alt-chain TX since TX could not be mined in 7 days txid=%s chain height %d tx height %d", txhash, height, v.Height) 230 delete_list = append(delete_list, txhash) 231 } 232 return true 233 }) 234 235 236 for i := range delete_list { 237 pool.Mempool_Delete_TX(delete_list[i]) 238 } 239 } 240 241 func (pool *Mempool) Shutdown() { 242 //TODO save mempool tx somewhere 243 244 close(pool.Exit_Mutex) // stop relaying 245 246 pool.Lock() 247 defer pool.Unlock() 248 249 mempool_file := filepath.Join(globals.GetDataDirectory(), "mempool.json") 250 251 // collect all txs in pool and serialize them and store them 252 var objects []mempool_object 253 254 255 pool.txs.Range(func(k, value interface{}) bool { 256 v := value.(*mempool_object) 257 objects = append(objects, *v) 258 return true 259 }) 260 261 /*for _, v := range pool.txs { 262 objects = append(objects, *v) 263 }*/ 264 265 var file, err = os.Create(mempool_file) 266 if err == nil { 267 defer file.Close() 268 encoder := json.NewEncoder(file) 269 encoder.SetIndent("", "\t") 270 err = encoder.Encode(objects) 271 272 if err != nil { 273 loggerpool.Warnf("Error marshaling mempool data err %s", err) 274 } 275 276 } else { 277 loggerpool.Warnf("Error creating new file to store mempool data file %s err %s", mempool_file, err) 278 } 279 280 loggerpool.Infof("Succesfully saved %d txs to file", (len(objects))) 281 282 loggerpool.Infof("Mempool stopped") 283 atomic.AddUint32(&globals.Subsystem_Active, ^uint32(0)) // this decrement 1 fom subsystem 284 285 } 286 287 // start pool monitoring for changes for some specific time 288 // this is required so as we can add or discard transactions while selecting work for mining 289 func (pool *Mempool) Monitor() { 290 pool.Lock() 291 pool.modified = false 292 pool.Unlock() 293 } 294 295 // return whether pool contents have changed 296 func (pool *Mempool) HasChanged() (result bool) { 297 pool.Lock() 298 result = pool.modified 299 pool.Unlock() 300 return 301 } 302 303 // a tx should only be added to pool after verification is complete 304 func (pool *Mempool) Mempool_Add_TX(tx *transaction.Transaction, Height uint64) (result bool) { 305 result = false 306 pool.Lock() 307 defer pool.Unlock() 308 309 var object mempool_object 310 311 tx_hash := crypto.Hash(tx.GetHash()) 312 313 // check if tx already exists, skip it 314 if _, ok := pool.txs.Load(tx_hash); ok { 315 //rlog.Debugf("Pool already contains %s, skipping", tx_hash) 316 return false 317 } 318 319 // we should also extract all key images and add them to have multiple pending 320 for i := 0; i < len(tx.Vin); i++ { 321 if _, ok := pool.key_images.Load(tx.Vin[i].(transaction.Txin_to_key).K_image); ok { 322 rlog.Warnf("TX using inputs which have already been used, Possible Double spend attack rejected txid %s kimage %s", tx_hash, 323 tx.Vin[i].(transaction.Txin_to_key).K_image) 324 return false 325 } 326 } 327 328 // add all the key images to check double spend attack within the pool 329 for i := 0; i < len(tx.Vin); i++ { 330 pool.key_images.Store(tx.Vin[i].(transaction.Txin_to_key).K_image,true) // add element to map for next check 331 } 332 333 // we are here means we can add it to pool 334 object.Tx = tx 335 object.Height = Height 336 object.Added = uint64(time.Now().UTC().Unix()) 337 338 object.Size = uint64(len(tx.Serialize())) 339 object.FEEperBYTE = tx.RctSignature.Get_TX_Fee() / object.Size 340 341 pool.txs.Store(tx_hash,&object) 342 pool.modified = true // pool has been modified 343 344 //pool.sort_list() // sort and update pool list 345 346 return true 347 } 348 349 // check whether a tx exists in the pool 350 func (pool *Mempool) Mempool_TX_Exist(txid crypto.Hash) (result bool) { 351 //pool.Lock() 352 //defer pool.Unlock() 353 354 if _, ok := pool.txs.Load(txid); ok { 355 return true 356 } 357 return false 358 } 359 360 // check whether a keyimage exists in the pool 361 func (pool *Mempool) Mempool_Keyimage_Spent(ki crypto.Hash) (result bool) { 362 //pool.Lock() 363 //defer pool.Unlock() 364 365 if _, ok := pool.key_images.Load(ki); ok { 366 return true 367 } 368 return false 369 } 370 371 // delete specific tx from pool and return it 372 // if nil is returned Tx was not found in pool 373 func (pool *Mempool) Mempool_Delete_TX(txid crypto.Hash) (tx *transaction.Transaction) { 374 //pool.Lock() 375 //defer pool.Unlock() 376 377 var ok bool 378 var objecti interface{} 379 380 // check if tx already exists, skip it 381 if objecti, ok = pool.txs.Load(txid); !ok { 382 rlog.Warnf("Pool does NOT contain %s, returning nil", txid) 383 return nil 384 } 385 386 387 388 // we reached here means, we have the tx remove it from our list, do maintainance cleapup and discard it 389 object := objecti.(*mempool_object) 390 pool.txs.Delete(txid) 391 392 // remove all the key images 393 for i := 0; i < len(object.Tx.Vin); i++ { 394 pool.key_images.Delete(object.Tx.Vin[i].(transaction.Txin_to_key).K_image) 395 } 396 397 //pool.sort_list() // sort and update pool list 398 pool.modified = true // pool has been modified 399 return object.Tx // return the tx 400 } 401 402 // get specific tx from mem pool without removing it 403 func (pool *Mempool) Mempool_Get_TX(txid crypto.Hash) (tx *transaction.Transaction) { 404 // pool.Lock() 405 // defer pool.Unlock() 406 407 var ok bool 408 var objecti interface{} 409 410 if objecti, ok = pool.txs.Load(txid); !ok { 411 //loggerpool.Warnf("Pool does NOT contain %s, returning nil", txid) 412 return nil 413 } 414 415 // we reached here means, we have the tx, return the pointer back 416 //object := pool.txs[txid] 417 object := objecti.(*mempool_object) 418 419 return object.Tx 420 } 421 422 // return list of all txs in pool 423 func (pool *Mempool) Mempool_List_TX() []crypto.Hash { 424 // pool.Lock() 425 // defer pool.Unlock() 426 427 var list []crypto.Hash 428 429 pool.txs.Range(func(k, value interface{}) bool { 430 txhash := k.(crypto.Hash) 431 //v := value.(*mempool_object) 432 //objects = append(objects, *v) 433 list = append(list,txhash) 434 return true 435 }) 436 437 //pool.sort_list() // sort and update pool list 438 439 // list should be as big as spurce list 440 //list := make([]crypto.Hash, len(pool.sorted_by_fee), len(pool.sorted_by_fee)) 441 //copy(list, pool.sorted_by_fee) // return list sorted by fees 442 443 return list 444 } 445 446 // passes back sorting information and length information for easier new block forging 447 func (pool *Mempool) Mempool_List_TX_SortedInfo() []TX_Sorting_struct { 448 // pool.Lock() 449 // defer pool.Unlock() 450 451 _, data := pool.sort_list() // sort and update pool list 452 return data 453 454 /* // list should be as big as spurce list 455 list := make([]TX_Sorting_struct, len(pool.sorted), len(pool.sorted)) 456 copy(list, pool.sorted) // return list sorted by fees 457 458 return list 459 */ 460 } 461 462 // print current mempool txs 463 // TODO add sorting 464 func (pool *Mempool) Mempool_Print() { 465 pool.Lock() 466 defer pool.Unlock() 467 468 var klist []crypto.Hash 469 var vlist []*mempool_object 470 471 pool.txs.Range(func(k, value interface{}) bool { 472 txhash := k.(crypto.Hash) 473 v := value.(*mempool_object) 474 //objects = append(objects, *v) 475 klist = append(klist,txhash) 476 vlist = append(vlist,v) 477 478 return true 479 }) 480 481 482 fmt.Printf("Total TX in mempool = %d\n", len(klist)) 483 fmt.Printf("%20s %14s %7s %7s %6s %32s\n", "Added", "Last Relayed", "Relayed", "Size", "Height", "TXID") 484 485 for i := range klist { 486 k := klist[i] 487 v := vlist[i] 488 fmt.Printf("%20s %14s %7d %7d %6d %32s\n", time.Unix(int64(v.Added), 0).UTC().Format(time.RFC3339), time.Duration(v.RelayedAt)*time.Second, v.Relayed, 489 len(v.Tx.Serialize()), v.Height, k) 490 } 491 } 492 493 // flush mempool 494 func (pool *Mempool) Mempool_flush() { 495 var list []crypto.Hash 496 497 pool.txs.Range(func(k, value interface{}) bool { 498 txhash := k.(crypto.Hash) 499 //v := value.(*mempool_object) 500 //objects = append(objects, *v) 501 list = append(list,txhash) 502 return true 503 }) 504 505 fmt.Printf("Total TX in mempool = %d \n", len(list)) 506 fmt.Printf("Flushing mempool \n") 507 508 509 for i := range list { 510 pool.Mempool_Delete_TX(list[i]) 511 } 512 } 513 514 // sorts the pool internally 515 // this function assummes lock is already taken 516 // ??? if we selecting transactions randomly, why to keep them sorted 517 func (pool *Mempool) sort_list() ([]crypto.Hash, []TX_Sorting_struct){ 518 519 data := make([]TX_Sorting_struct,0,512) // we are rarely expectingmore than this entries in mempool 520 // collect data from pool for sorting 521 522 pool.txs.Range(func(k, value interface{}) bool { 523 txhash := k.(crypto.Hash) 524 v := value.(*mempool_object) 525 if v.Height <= pool.height { 526 data = append(data, TX_Sorting_struct{Hash: txhash, FeesPerByte: v.FEEperBYTE, Size: v.Size}) 527 } 528 return true 529 }) 530 531 532 // inverted comparision sort to do descending sort 533 sort.SliceStable(data, func(i, j int) bool { return data[i].FeesPerByte > data[j].FeesPerByte }) 534 535 sorted_list := make([]crypto.Hash,0,len(data)) 536 //pool.sorted_by_fee = pool.sorted_by_fee[:0] // empty old slice 537 538 for i := range data { 539 sorted_list = append(sorted_list, data[i].Hash) 540 } 541 //pool.sorted = data 542 return sorted_list, data 543 544 } 545 546 type p2p_TX_Relayer func(*transaction.Transaction, uint64) int // function type, exported in p2p but cannot use due to cyclic dependency 547 548 // this tx relayer keeps on relaying tx and cleaning mempool 549 // if a tx has been relayed less than 10 peers, tx relaying is agressive 550 // otherwise the tx are relayed every 30 minutes, till it has been relayed to 20 551 // then the tx is relayed every 3 hours, just in case 552 func (pool *Mempool) Relayer_and_Cleaner() { 553 554 for { 555 556 select { 557 case <-pool.Exit_Mutex: 558 return 559 case <-time.After(4000 * time.Millisecond): 560 561 } 562 563 sent_count := 0 564 565 //pool.Lock() 566 567 //loggerpool.Warnf("send Pool lock taken") 568 569 570 pool.txs.Range(func(ktmp, value interface{}) bool { 571 k := ktmp.(crypto.Hash) 572 v := value.(*mempool_object) 573 574 select { // exit fast of possible 575 case <-pool.Exit_Mutex: 576 pool.Unlock() 577 return false 578 default: 579 } 580 581 if sent_count > 200 { // send a burst of 200 txs max in 1 go 582 return false; 583 } 584 585 if v.Height <= pool.height { // only carry out activities for valid txs 586 587 if (v.Relayed < 3 && (time.Now().Unix()-v.RelayedAt) > 60) || // relay it now 588 (v.Relayed >= 4 && v.Relayed <= 20 && (time.Now().Unix()-v.RelayedAt) > 1200) || // relay it now 589 ((time.Now().Unix() - v.RelayedAt) > (3600 * 3)) { 590 if pool.P2P_TX_Relayer != nil { 591 592 relayed_count := pool.P2P_TX_Relayer(v.Tx, 0) 593 //relayed_count := 0 594 if relayed_count > 0 { 595 v.Relayed += relayed_count 596 597 sent_count++ 598 599 //loggerpool.Debugf("%d %d\n",time.Now().Unix(), v.RelayedAt) 600 rlog.Tracef(1,"Relayed %s to %d peers (%d %d)", k, relayed_count, v.Relayed, (time.Now().Unix() - v.RelayedAt)) 601 v.RelayedAt = time.Now().Unix() 602 //loggerpool.Debugf("%d %d",time.Now().Unix(), v.RelayedAt) 603 } 604 } 605 } 606 } 607 608 return true 609 }) 610 611 612 // loggerpool.Warnf("send Pool lock released") 613 //pool.Unlock() 614 } 615 }