github.com/neatio-net/neatio@v1.7.3-0.20231114194659-f4d7a2226baa/neatptc/downloader/downloader_test.go (about)

     1  package downloader
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"math/big"
     7  	"sync"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/neatio-net/neatio/chain/core"
    12  	"github.com/neatio-net/neatio/chain/core/rawdb"
    13  	"github.com/neatio-net/neatio/chain/core/types"
    14  	"github.com/neatio-net/neatio/chain/trie"
    15  	"github.com/neatio-net/neatio/neatdb"
    16  	"github.com/neatio-net/neatio/params"
    17  	"github.com/neatio-net/neatio/utilities/common"
    18  	"github.com/neatio-net/neatio/utilities/crypto"
    19  	"github.com/neatio-net/neatio/utilities/event"
    20  )
    21  
    22  var (
    23  	testKey, _  = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
    24  	testAddress = crypto.PubkeyToAddress(testKey.PublicKey)
    25  )
    26  
    27  func init() {
    28  	MaxForkAncestry = uint64(10000)
    29  	blockCacheItems = 1024
    30  	fsHeaderContCheck = 500 * time.Millisecond
    31  }
    32  
    33  type downloadTester struct {
    34  	downloader *Downloader
    35  
    36  	genesis *types.Block
    37  	stateDb neatdb.Database
    38  	peerDb  neatdb.Database
    39  
    40  	ownHashes   []common.Hash
    41  	ownHeaders  map[common.Hash]*types.Header
    42  	ownBlocks   map[common.Hash]*types.Block
    43  	ownReceipts map[common.Hash]types.Receipts
    44  	ownChainTd  map[common.Hash]*big.Int
    45  
    46  	peerHashes   map[string][]common.Hash
    47  	peerHeaders  map[string]map[common.Hash]*types.Header
    48  	peerBlocks   map[string]map[common.Hash]*types.Block
    49  	peerReceipts map[string]map[common.Hash]types.Receipts
    50  	peerChainTds map[string]map[common.Hash]*big.Int
    51  
    52  	peerMissingStates map[string]map[common.Hash]bool
    53  
    54  	lock sync.RWMutex
    55  }
    56  
    57  func newTester() *downloadTester {
    58  	testdb := rawdb.NewMemoryDatabase()
    59  	genesis := core.GenesisBlockForTesting(testdb, testAddress, big.NewInt(1000000000))
    60  
    61  	tester := &downloadTester{
    62  		genesis:           genesis,
    63  		peerDb:            testdb,
    64  		ownHashes:         []common.Hash{genesis.Hash()},
    65  		ownHeaders:        map[common.Hash]*types.Header{genesis.Hash(): genesis.Header()},
    66  		ownBlocks:         map[common.Hash]*types.Block{genesis.Hash(): genesis},
    67  		ownReceipts:       map[common.Hash]types.Receipts{genesis.Hash(): nil},
    68  		ownChainTd:        map[common.Hash]*big.Int{genesis.Hash(): genesis.Difficulty()},
    69  		peerHashes:        make(map[string][]common.Hash),
    70  		peerHeaders:       make(map[string]map[common.Hash]*types.Header),
    71  		peerBlocks:        make(map[string]map[common.Hash]*types.Block),
    72  		peerReceipts:      make(map[string]map[common.Hash]types.Receipts),
    73  		peerChainTds:      make(map[string]map[common.Hash]*big.Int),
    74  		peerMissingStates: make(map[string]map[common.Hash]bool),
    75  	}
    76  	tester.stateDb = rawdb.NewMemoryDatabase()
    77  	tester.stateDb.Put(genesis.Root().Bytes(), []byte{0x00})
    78  
    79  	tester.downloader = New(FullSync, tester.stateDb, new(event.TypeMux), tester, nil, tester.dropPeer, nil)
    80  
    81  	return tester
    82  }
    83  
    84  func (dl *downloadTester) makeChain(n int, seed byte, parent *types.Block, parentReceipts types.Receipts, heavy bool) ([]common.Hash, map[common.Hash]*types.Header, map[common.Hash]*types.Block, map[common.Hash]types.Receipts) {
    85  
    86  	blocks, receipts := core.GenerateChain(params.TestChainConfig, parent, nil, dl.peerDb, n, func(i int, block *core.BlockGen) {
    87  		block.SetCoinbase(common.Address{seed})
    88  
    89  		if heavy {
    90  			block.OffsetTime(-1)
    91  		}
    92  
    93  		if parent == dl.genesis && i%3 == 0 {
    94  			signer := types.MakeSigner(params.TestChainConfig, block.Number())
    95  			tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, nil, nil), signer, testKey)
    96  			if err != nil {
    97  				panic(err)
    98  			}
    99  			block.AddTx(tx)
   100  		}
   101  
   102  		if i > 0 && i%5 == 0 {
   103  			block.AddUncle(&types.Header{
   104  				ParentHash: block.PrevBlock(i - 1).Hash(),
   105  				Number:     big.NewInt(block.Number().Int64() - 1),
   106  			})
   107  		}
   108  	})
   109  
   110  	hashes := make([]common.Hash, n+1)
   111  	hashes[len(hashes)-1] = parent.Hash()
   112  
   113  	headerm := make(map[common.Hash]*types.Header, n+1)
   114  	headerm[parent.Hash()] = parent.Header()
   115  
   116  	blockm := make(map[common.Hash]*types.Block, n+1)
   117  	blockm[parent.Hash()] = parent
   118  
   119  	receiptm := make(map[common.Hash]types.Receipts, n+1)
   120  	receiptm[parent.Hash()] = parentReceipts
   121  
   122  	for i, b := range blocks {
   123  		hashes[len(hashes)-i-2] = b.Hash()
   124  		headerm[b.Hash()] = b.Header()
   125  		blockm[b.Hash()] = b
   126  		receiptm[b.Hash()] = receipts[i]
   127  	}
   128  	return hashes, headerm, blockm, receiptm
   129  }
   130  
   131  func (dl *downloadTester) makeChainFork(n, f int, parent *types.Block, parentReceipts types.Receipts, balanced bool) ([]common.Hash, []common.Hash, map[common.Hash]*types.Header, map[common.Hash]*types.Header, map[common.Hash]*types.Block, map[common.Hash]*types.Block, map[common.Hash]types.Receipts, map[common.Hash]types.Receipts) {
   132  
   133  	hashes, headers, blocks, receipts := dl.makeChain(n-f, 0, parent, parentReceipts, false)
   134  
   135  	hashes1, headers1, blocks1, receipts1 := dl.makeChain(f, 1, blocks[hashes[0]], receipts[hashes[0]], false)
   136  	hashes1 = append(hashes1, hashes[1:]...)
   137  
   138  	heavy := false
   139  	if !balanced {
   140  		heavy = true
   141  	}
   142  	hashes2, headers2, blocks2, receipts2 := dl.makeChain(f, 2, blocks[hashes[0]], receipts[hashes[0]], heavy)
   143  	hashes2 = append(hashes2, hashes[1:]...)
   144  
   145  	for hash, header := range headers {
   146  		headers1[hash] = header
   147  		headers2[hash] = header
   148  	}
   149  	for hash, block := range blocks {
   150  		blocks1[hash] = block
   151  		blocks2[hash] = block
   152  	}
   153  	for hash, receipt := range receipts {
   154  		receipts1[hash] = receipt
   155  		receipts2[hash] = receipt
   156  	}
   157  	return hashes1, hashes2, headers1, headers2, blocks1, blocks2, receipts1, receipts2
   158  }
   159  
   160  func (dl *downloadTester) terminate() {
   161  	dl.downloader.Terminate()
   162  }
   163  
   164  func (dl *downloadTester) sync(id string, td *big.Int, mode SyncMode) error {
   165  	dl.lock.RLock()
   166  	hash := dl.peerHashes[id][0]
   167  
   168  	if td == nil {
   169  		td = big.NewInt(1)
   170  		if diff, ok := dl.peerChainTds[id][hash]; ok {
   171  			td = diff
   172  		}
   173  	}
   174  	dl.lock.RUnlock()
   175  
   176  	err := dl.downloader.synchronise(id, hash, td, mode)
   177  	select {
   178  	case <-dl.downloader.cancelCh:
   179  
   180  	default:
   181  
   182  		panic("downloader active post sync cycle")
   183  	}
   184  	return err
   185  }
   186  
   187  func (dl *downloadTester) HasHeader(hash common.Hash, number uint64) bool {
   188  	return dl.GetHeaderByHash(hash) != nil
   189  }
   190  
   191  func (dl *downloadTester) HasBlock(hash common.Hash, number uint64) bool {
   192  	return dl.GetBlockByHash(hash) != nil
   193  }
   194  
   195  func (dl *downloadTester) GetHeaderByHash(hash common.Hash) *types.Header {
   196  	dl.lock.RLock()
   197  	defer dl.lock.RUnlock()
   198  
   199  	return dl.ownHeaders[hash]
   200  }
   201  
   202  func (dl *downloadTester) GetBlockByHash(hash common.Hash) *types.Block {
   203  	dl.lock.RLock()
   204  	defer dl.lock.RUnlock()
   205  
   206  	return dl.ownBlocks[hash]
   207  }
   208  
   209  func (dl *downloadTester) CurrentHeader() *types.Header {
   210  	dl.lock.RLock()
   211  	defer dl.lock.RUnlock()
   212  
   213  	for i := len(dl.ownHashes) - 1; i >= 0; i-- {
   214  		if header := dl.ownHeaders[dl.ownHashes[i]]; header != nil {
   215  			return header
   216  		}
   217  	}
   218  	return dl.genesis.Header()
   219  }
   220  
   221  func (dl *downloadTester) CurrentBlock() *types.Block {
   222  	dl.lock.RLock()
   223  	defer dl.lock.RUnlock()
   224  
   225  	for i := len(dl.ownHashes) - 1; i >= 0; i-- {
   226  		if block := dl.ownBlocks[dl.ownHashes[i]]; block != nil {
   227  			if _, err := dl.stateDb.Get(block.Root().Bytes()); err == nil {
   228  				return block
   229  			}
   230  		}
   231  	}
   232  	return dl.genesis
   233  }
   234  
   235  func (dl *downloadTester) CurrentFastBlock() *types.Block {
   236  	dl.lock.RLock()
   237  	defer dl.lock.RUnlock()
   238  
   239  	for i := len(dl.ownHashes) - 1; i >= 0; i-- {
   240  		if block := dl.ownBlocks[dl.ownHashes[i]]; block != nil {
   241  			return block
   242  		}
   243  	}
   244  	return dl.genesis
   245  }
   246  
   247  func (dl *downloadTester) FastSyncCommitHead(hash common.Hash) error {
   248  
   249  	if block := dl.GetBlockByHash(hash); block != nil {
   250  		_, err := trie.NewSecure(block.Root(), trie.NewDatabase(dl.stateDb))
   251  		return err
   252  	}
   253  	return fmt.Errorf("non existent block: %x", hash[:4])
   254  }
   255  
   256  func (dl *downloadTester) GetTd(hash common.Hash, number uint64) *big.Int {
   257  	dl.lock.RLock()
   258  	defer dl.lock.RUnlock()
   259  
   260  	return dl.ownChainTd[hash]
   261  }
   262  
   263  func (dl *downloadTester) InsertHeaderChain(headers []*types.Header, checkFreq int) (int, error) {
   264  	dl.lock.Lock()
   265  	defer dl.lock.Unlock()
   266  
   267  	if _, ok := dl.ownHeaders[headers[0].ParentHash]; !ok {
   268  		return 0, errors.New("unknown parent")
   269  	}
   270  	for i := 1; i < len(headers); i++ {
   271  		if headers[i].ParentHash != headers[i-1].Hash() {
   272  			return i, errors.New("unknown parent")
   273  		}
   274  	}
   275  
   276  	for i, header := range headers {
   277  		if _, ok := dl.ownHeaders[header.Hash()]; ok {
   278  			continue
   279  		}
   280  		if _, ok := dl.ownHeaders[header.ParentHash]; !ok {
   281  			return i, errors.New("unknown parent")
   282  		}
   283  		dl.ownHashes = append(dl.ownHashes, header.Hash())
   284  		dl.ownHeaders[header.Hash()] = header
   285  		dl.ownChainTd[header.Hash()] = new(big.Int).Add(dl.ownChainTd[header.ParentHash], header.Difficulty)
   286  	}
   287  	return len(headers), nil
   288  }
   289  
   290  func (dl *downloadTester) InsertChain(blocks types.Blocks) (int, error) {
   291  	dl.lock.Lock()
   292  	defer dl.lock.Unlock()
   293  
   294  	for i, block := range blocks {
   295  		if parent, ok := dl.ownBlocks[block.ParentHash()]; !ok {
   296  			return i, errors.New("unknown parent")
   297  		} else if _, err := dl.stateDb.Get(parent.Root().Bytes()); err != nil {
   298  			return i, fmt.Errorf("unknown parent state %x: %v", parent.Root(), err)
   299  		}
   300  		if _, ok := dl.ownHeaders[block.Hash()]; !ok {
   301  			dl.ownHashes = append(dl.ownHashes, block.Hash())
   302  			dl.ownHeaders[block.Hash()] = block.Header()
   303  		}
   304  		dl.ownBlocks[block.Hash()] = block
   305  		dl.stateDb.Put(block.Root().Bytes(), []byte{0x00})
   306  		dl.ownChainTd[block.Hash()] = new(big.Int).Add(dl.ownChainTd[block.ParentHash()], block.Difficulty())
   307  	}
   308  	return len(blocks), nil
   309  }
   310  
   311  func (dl *downloadTester) InsertReceiptChain(blocks types.Blocks, receipts []types.Receipts) (int, error) {
   312  	dl.lock.Lock()
   313  	defer dl.lock.Unlock()
   314  
   315  	for i := 0; i < len(blocks) && i < len(receipts); i++ {
   316  		if _, ok := dl.ownHeaders[blocks[i].Hash()]; !ok {
   317  			return i, errors.New("unknown owner")
   318  		}
   319  		if _, ok := dl.ownBlocks[blocks[i].ParentHash()]; !ok {
   320  			return i, errors.New("unknown parent")
   321  		}
   322  		dl.ownBlocks[blocks[i].Hash()] = blocks[i]
   323  		dl.ownReceipts[blocks[i].Hash()] = receipts[i]
   324  	}
   325  	return len(blocks), nil
   326  }
   327  
   328  func (dl *downloadTester) Rollback(hashes []common.Hash) {
   329  	dl.lock.Lock()
   330  	defer dl.lock.Unlock()
   331  
   332  	for i := len(hashes) - 1; i >= 0; i-- {
   333  		if dl.ownHashes[len(dl.ownHashes)-1] == hashes[i] {
   334  			dl.ownHashes = dl.ownHashes[:len(dl.ownHashes)-1]
   335  		}
   336  		delete(dl.ownChainTd, hashes[i])
   337  		delete(dl.ownHeaders, hashes[i])
   338  		delete(dl.ownReceipts, hashes[i])
   339  		delete(dl.ownBlocks, hashes[i])
   340  	}
   341  }
   342  
   343  func (dl *downloadTester) newPeer(id string, version int, hashes []common.Hash, headers map[common.Hash]*types.Header, blocks map[common.Hash]*types.Block, receipts map[common.Hash]types.Receipts) error {
   344  	return dl.newSlowPeer(id, version, hashes, headers, blocks, receipts, 0)
   345  }
   346  
   347  func (dl *downloadTester) newSlowPeer(id string, version int, hashes []common.Hash, headers map[common.Hash]*types.Header, blocks map[common.Hash]*types.Block, receipts map[common.Hash]types.Receipts, delay time.Duration) error {
   348  	dl.lock.Lock()
   349  	defer dl.lock.Unlock()
   350  
   351  	var err = dl.downloader.RegisterPeer(id, version, &downloadTesterPeer{dl: dl, id: id, delay: delay})
   352  	if err == nil {
   353  
   354  		dl.peerHashes[id] = make([]common.Hash, len(hashes))
   355  		copy(dl.peerHashes[id], hashes)
   356  
   357  		dl.peerHeaders[id] = make(map[common.Hash]*types.Header)
   358  		dl.peerBlocks[id] = make(map[common.Hash]*types.Block)
   359  		dl.peerReceipts[id] = make(map[common.Hash]types.Receipts)
   360  		dl.peerChainTds[id] = make(map[common.Hash]*big.Int)
   361  		dl.peerMissingStates[id] = make(map[common.Hash]bool)
   362  
   363  		genesis := hashes[len(hashes)-1]
   364  		if header := headers[genesis]; header != nil {
   365  			dl.peerHeaders[id][genesis] = header
   366  			dl.peerChainTds[id][genesis] = header.Difficulty
   367  		}
   368  		if block := blocks[genesis]; block != nil {
   369  			dl.peerBlocks[id][genesis] = block
   370  			dl.peerChainTds[id][genesis] = block.Difficulty()
   371  		}
   372  
   373  		for i := len(hashes) - 2; i >= 0; i-- {
   374  			hash := hashes[i]
   375  
   376  			if header, ok := headers[hash]; ok {
   377  				dl.peerHeaders[id][hash] = header
   378  				if _, ok := dl.peerHeaders[id][header.ParentHash]; ok {
   379  					dl.peerChainTds[id][hash] = new(big.Int).Add(header.Difficulty, dl.peerChainTds[id][header.ParentHash])
   380  				}
   381  			}
   382  			if block, ok := blocks[hash]; ok {
   383  				dl.peerBlocks[id][hash] = block
   384  				if _, ok := dl.peerBlocks[id][block.ParentHash()]; ok {
   385  					dl.peerChainTds[id][hash] = new(big.Int).Add(block.Difficulty(), dl.peerChainTds[id][block.ParentHash()])
   386  				}
   387  			}
   388  			if receipt, ok := receipts[hash]; ok {
   389  				dl.peerReceipts[id][hash] = receipt
   390  			}
   391  		}
   392  	}
   393  	return err
   394  }
   395  
   396  func (dl *downloadTester) dropPeer(id string) {
   397  	dl.lock.Lock()
   398  	defer dl.lock.Unlock()
   399  
   400  	delete(dl.peerHashes, id)
   401  	delete(dl.peerHeaders, id)
   402  	delete(dl.peerBlocks, id)
   403  	delete(dl.peerChainTds, id)
   404  
   405  	dl.downloader.UnregisterPeer(id)
   406  }
   407  
   408  type downloadTesterPeer struct {
   409  	dl    *downloadTester
   410  	id    string
   411  	delay time.Duration
   412  	lock  sync.RWMutex
   413  }
   414  
   415  func (dlp *downloadTesterPeer) setDelay(delay time.Duration) {
   416  	dlp.lock.Lock()
   417  	defer dlp.lock.Unlock()
   418  
   419  	dlp.delay = delay
   420  }
   421  
   422  func (dlp *downloadTesterPeer) waitDelay() {
   423  	dlp.lock.RLock()
   424  	delay := dlp.delay
   425  	dlp.lock.RUnlock()
   426  
   427  	time.Sleep(delay)
   428  }
   429  
   430  func (dlp *downloadTesterPeer) Head() (common.Hash, *big.Int) {
   431  	dlp.dl.lock.RLock()
   432  	defer dlp.dl.lock.RUnlock()
   433  
   434  	return dlp.dl.peerHashes[dlp.id][0], nil
   435  }
   436  
   437  func (dlp *downloadTesterPeer) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool) error {
   438  
   439  	dlp.dl.lock.RLock()
   440  	number := uint64(0)
   441  	for num, hash := range dlp.dl.peerHashes[dlp.id] {
   442  		if hash == origin {
   443  			number = uint64(len(dlp.dl.peerHashes[dlp.id]) - num - 1)
   444  			break
   445  		}
   446  	}
   447  	dlp.dl.lock.RUnlock()
   448  
   449  	return dlp.RequestHeadersByNumber(number, amount, skip, reverse)
   450  }
   451  
   452  func (dlp *downloadTesterPeer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool) error {
   453  	dlp.waitDelay()
   454  
   455  	dlp.dl.lock.RLock()
   456  	defer dlp.dl.lock.RUnlock()
   457  
   458  	hashes := dlp.dl.peerHashes[dlp.id]
   459  	headers := dlp.dl.peerHeaders[dlp.id]
   460  	result := make([]*types.Header, 0, amount)
   461  	for i := 0; i < amount && len(hashes)-int(origin)-1-i*(skip+1) >= 0; i++ {
   462  		if header, ok := headers[hashes[len(hashes)-int(origin)-1-i*(skip+1)]]; ok {
   463  			result = append(result, header)
   464  		}
   465  	}
   466  
   467  	go func() {
   468  		time.Sleep(time.Millisecond)
   469  		dlp.dl.downloader.DeliverHeaders(dlp.id, result)
   470  	}()
   471  	return nil
   472  }
   473  
   474  func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash) error {
   475  	dlp.waitDelay()
   476  
   477  	dlp.dl.lock.RLock()
   478  	defer dlp.dl.lock.RUnlock()
   479  
   480  	blocks := dlp.dl.peerBlocks[dlp.id]
   481  
   482  	transactions := make([][]*types.Transaction, 0, len(hashes))
   483  	uncles := make([][]*types.Header, 0, len(hashes))
   484  
   485  	for _, hash := range hashes {
   486  		if block, ok := blocks[hash]; ok {
   487  			transactions = append(transactions, block.Transactions())
   488  			uncles = append(uncles, block.Uncles())
   489  		}
   490  	}
   491  	go dlp.dl.downloader.DeliverBodies(dlp.id, transactions, uncles)
   492  
   493  	return nil
   494  }
   495  
   496  func (dlp *downloadTesterPeer) RequestReceipts(hashes []common.Hash) error {
   497  	dlp.waitDelay()
   498  
   499  	dlp.dl.lock.RLock()
   500  	defer dlp.dl.lock.RUnlock()
   501  
   502  	receipts := dlp.dl.peerReceipts[dlp.id]
   503  
   504  	results := make([][]*types.Receipt, 0, len(hashes))
   505  	for _, hash := range hashes {
   506  		if receipt, ok := receipts[hash]; ok {
   507  			results = append(results, receipt)
   508  		}
   509  	}
   510  	go dlp.dl.downloader.DeliverReceipts(dlp.id, results)
   511  
   512  	return nil
   513  }
   514  
   515  func (dlp *downloadTesterPeer) RequestNodeData(hashes []common.Hash) error {
   516  	dlp.waitDelay()
   517  
   518  	dlp.dl.lock.RLock()
   519  	defer dlp.dl.lock.RUnlock()
   520  
   521  	results := make([][]byte, 0, len(hashes))
   522  	for _, hash := range hashes {
   523  		if data, err := dlp.dl.peerDb.Get(hash.Bytes()); err == nil {
   524  			if !dlp.dl.peerMissingStates[dlp.id][hash] {
   525  				results = append(results, data)
   526  			}
   527  		}
   528  	}
   529  	go dlp.dl.downloader.DeliverNodeData(dlp.id, results)
   530  
   531  	return nil
   532  }
   533  
   534  func assertOwnChain(t *testing.T, tester *downloadTester, length int) {
   535  	assertOwnForkedChain(t, tester, 1, []int{length})
   536  }
   537  
   538  func assertOwnForkedChain(t *testing.T, tester *downloadTester, common int, lengths []int) {
   539  
   540  	headers, blocks, receipts := lengths[0], lengths[0], lengths[0]-fsMinFullBlocks
   541  
   542  	if receipts < 0 {
   543  		receipts = 1
   544  	}
   545  
   546  	for _, length := range lengths[1:] {
   547  		headers += length - common
   548  		blocks += length - common
   549  		receipts += length - common - fsMinFullBlocks
   550  	}
   551  	switch tester.downloader.mode {
   552  	case FullSync:
   553  		receipts = 1
   554  		if hs := len(tester.ownHeaders); hs != headers {
   555  			t.Fatalf("synchronised headers mismatch: have %v, want %v", hs, headers)
   556  		}
   557  		if bs := len(tester.ownBlocks); bs != blocks {
   558  			t.Fatalf("synchronised blocks mismatch: have %v, want %v", bs, blocks)
   559  		}
   560  		if rs := len(tester.ownReceipts); rs != receipts {
   561  			t.Fatalf("synchronised receipts mismatch: have %v, want %v", rs, receipts)
   562  		}
   563  
   564  	}
   565  }