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  }