github.com/tmoore22/go-ethereum@v1.10.22-0.20220814113424-76f4d8bc4994/trie/sync_test.go (about)

     1  // Copyright 2015 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package trie
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"testing"
    23  
    24  	"github.com/ethereum/go-ethereum/common"
    25  	"github.com/ethereum/go-ethereum/crypto"
    26  	"github.com/ethereum/go-ethereum/ethdb/memorydb"
    27  )
    28  
    29  // makeTestTrie create a sample test trie to test node-wise reconstruction.
    30  func makeTestTrie() (*Database, *StateTrie, map[string][]byte) {
    31  	// Create an empty trie
    32  	triedb := NewDatabase(memorydb.New())
    33  	trie, _ := NewStateTrie(common.Hash{}, common.Hash{}, triedb)
    34  
    35  	// Fill it with some arbitrary data
    36  	content := make(map[string][]byte)
    37  	for i := byte(0); i < 255; i++ {
    38  		// Map the same data under multiple keys
    39  		key, val := common.LeftPadBytes([]byte{1, i}, 32), []byte{i}
    40  		content[string(key)] = val
    41  		trie.Update(key, val)
    42  
    43  		key, val = common.LeftPadBytes([]byte{2, i}, 32), []byte{i}
    44  		content[string(key)] = val
    45  		trie.Update(key, val)
    46  
    47  		// Add some other data to inflate the trie
    48  		for j := byte(3); j < 13; j++ {
    49  			key, val = common.LeftPadBytes([]byte{j, i}, 32), []byte{j, i}
    50  			content[string(key)] = val
    51  			trie.Update(key, val)
    52  		}
    53  	}
    54  	root, nodes, err := trie.Commit(false)
    55  	if err != nil {
    56  		panic(fmt.Errorf("failed to commit trie %v", err))
    57  	}
    58  	if err := triedb.Update(NewWithNodeSet(nodes)); err != nil {
    59  		panic(fmt.Errorf("failed to commit db %v", err))
    60  	}
    61  	// Re-create the trie based on the new state
    62  	trie, _ = NewSecure(common.Hash{}, root, triedb)
    63  	return triedb, trie, content
    64  }
    65  
    66  // checkTrieContents cross references a reconstructed trie with an expected data
    67  // content map.
    68  func checkTrieContents(t *testing.T, db *Database, root []byte, content map[string][]byte) {
    69  	// Check root availability and trie contents
    70  	trie, err := NewStateTrie(common.Hash{}, common.BytesToHash(root), db)
    71  	if err != nil {
    72  		t.Fatalf("failed to create trie at %x: %v", root, err)
    73  	}
    74  	if err := checkTrieConsistency(db, common.BytesToHash(root)); err != nil {
    75  		t.Fatalf("inconsistent trie at %x: %v", root, err)
    76  	}
    77  	for key, val := range content {
    78  		if have := trie.Get([]byte(key)); !bytes.Equal(have, val) {
    79  			t.Errorf("entry %x: content mismatch: have %x, want %x", key, have, val)
    80  		}
    81  	}
    82  }
    83  
    84  // checkTrieConsistency checks that all nodes in a trie are indeed present.
    85  func checkTrieConsistency(db *Database, root common.Hash) error {
    86  	// Create and iterate a trie rooted in a subnode
    87  	trie, err := NewStateTrie(common.Hash{}, root, db)
    88  	if err != nil {
    89  		return nil // Consider a non existent state consistent
    90  	}
    91  	it := trie.NodeIterator(nil)
    92  	for it.Next(true) {
    93  	}
    94  	return it.Error()
    95  }
    96  
    97  // trieElement represents the element in the state trie(bytecode or trie node).
    98  type trieElement struct {
    99  	path     string
   100  	hash     common.Hash
   101  	syncPath SyncPath
   102  }
   103  
   104  // Tests that an empty trie is not scheduled for syncing.
   105  func TestEmptySync(t *testing.T) {
   106  	dbA := NewDatabase(memorydb.New())
   107  	dbB := NewDatabase(memorydb.New())
   108  	emptyA := NewEmpty(dbA)
   109  	emptyB, _ := New(common.Hash{}, emptyRoot, dbB)
   110  
   111  	for i, trie := range []*Trie{emptyA, emptyB} {
   112  		sync := NewSync(trie.Hash(), memorydb.New(), nil)
   113  		if paths, nodes, codes := sync.Missing(1); len(paths) != 0 || len(nodes) != 0 || len(codes) != 0 {
   114  			t.Errorf("test %d: content requested for empty trie: %v, %v, %v", i, paths, nodes, codes)
   115  		}
   116  	}
   117  }
   118  
   119  // Tests that given a root hash, a trie can sync iteratively on a single thread,
   120  // requesting retrieval tasks and returning all of them in one go.
   121  func TestIterativeSyncIndividual(t *testing.T)       { testIterativeSync(t, 1, false) }
   122  func TestIterativeSyncBatched(t *testing.T)          { testIterativeSync(t, 100, false) }
   123  func TestIterativeSyncIndividualByPath(t *testing.T) { testIterativeSync(t, 1, true) }
   124  func TestIterativeSyncBatchedByPath(t *testing.T)    { testIterativeSync(t, 100, true) }
   125  
   126  func testIterativeSync(t *testing.T, count int, bypath bool) {
   127  	// Create a random trie to copy
   128  	srcDb, srcTrie, srcData := makeTestTrie()
   129  
   130  	// Create a destination trie and sync with the scheduler
   131  	diskdb := memorydb.New()
   132  	triedb := NewDatabase(diskdb)
   133  	sched := NewSync(srcTrie.Hash(), diskdb, nil)
   134  
   135  	// The code requests are ignored here since there is no code
   136  	// at the testing trie.
   137  	paths, nodes, _ := sched.Missing(count)
   138  	var elements []trieElement
   139  	for i := 0; i < len(paths); i++ {
   140  		elements = append(elements, trieElement{
   141  			path:     paths[i],
   142  			hash:     nodes[i],
   143  			syncPath: NewSyncPath([]byte(paths[i])),
   144  		})
   145  	}
   146  	for len(elements) > 0 {
   147  		results := make([]NodeSyncResult, len(elements))
   148  		if !bypath {
   149  			for i, element := range elements {
   150  				data, err := srcDb.Node(element.hash)
   151  				if err != nil {
   152  					t.Fatalf("failed to retrieve node data for hash %x: %v", element.hash, err)
   153  				}
   154  				results[i] = NodeSyncResult{element.path, data}
   155  			}
   156  		} else {
   157  			for i, element := range elements {
   158  				data, _, err := srcTrie.TryGetNode(element.syncPath[len(element.syncPath)-1])
   159  				if err != nil {
   160  					t.Fatalf("failed to retrieve node data for path %x: %v", element.path, err)
   161  				}
   162  				results[i] = NodeSyncResult{element.path, data}
   163  			}
   164  		}
   165  		for _, result := range results {
   166  			if err := sched.ProcessNode(result); err != nil {
   167  				t.Fatalf("failed to process result %v", err)
   168  			}
   169  		}
   170  		batch := diskdb.NewBatch()
   171  		if err := sched.Commit(batch); err != nil {
   172  			t.Fatalf("failed to commit data: %v", err)
   173  		}
   174  		batch.Write()
   175  
   176  		paths, nodes, _ = sched.Missing(count)
   177  		elements = elements[:0]
   178  		for i := 0; i < len(paths); i++ {
   179  			elements = append(elements, trieElement{
   180  				path:     paths[i],
   181  				hash:     nodes[i],
   182  				syncPath: NewSyncPath([]byte(paths[i])),
   183  			})
   184  		}
   185  	}
   186  	// Cross check that the two tries are in sync
   187  	checkTrieContents(t, triedb, srcTrie.Hash().Bytes(), srcData)
   188  }
   189  
   190  // Tests that the trie scheduler can correctly reconstruct the state even if only
   191  // partial results are returned, and the others sent only later.
   192  func TestIterativeDelayedSync(t *testing.T) {
   193  	// Create a random trie to copy
   194  	srcDb, srcTrie, srcData := makeTestTrie()
   195  
   196  	// Create a destination trie and sync with the scheduler
   197  	diskdb := memorydb.New()
   198  	triedb := NewDatabase(diskdb)
   199  	sched := NewSync(srcTrie.Hash(), diskdb, nil)
   200  
   201  	// The code requests are ignored here since there is no code
   202  	// at the testing trie.
   203  	paths, nodes, _ := sched.Missing(10000)
   204  	var elements []trieElement
   205  	for i := 0; i < len(paths); i++ {
   206  		elements = append(elements, trieElement{
   207  			path:     paths[i],
   208  			hash:     nodes[i],
   209  			syncPath: NewSyncPath([]byte(paths[i])),
   210  		})
   211  	}
   212  	for len(elements) > 0 {
   213  		// Sync only half of the scheduled nodes
   214  		results := make([]NodeSyncResult, len(elements)/2+1)
   215  		for i, element := range elements[:len(results)] {
   216  			data, err := srcDb.Node(element.hash)
   217  			if err != nil {
   218  				t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err)
   219  			}
   220  			results[i] = NodeSyncResult{element.path, data}
   221  		}
   222  		for _, result := range results {
   223  			if err := sched.ProcessNode(result); err != nil {
   224  				t.Fatalf("failed to process result %v", err)
   225  			}
   226  		}
   227  		batch := diskdb.NewBatch()
   228  		if err := sched.Commit(batch); err != nil {
   229  			t.Fatalf("failed to commit data: %v", err)
   230  		}
   231  		batch.Write()
   232  
   233  		paths, nodes, _ = sched.Missing(10000)
   234  		elements = elements[len(results):]
   235  		for i := 0; i < len(paths); i++ {
   236  			elements = append(elements, trieElement{
   237  				path:     paths[i],
   238  				hash:     nodes[i],
   239  				syncPath: NewSyncPath([]byte(paths[i])),
   240  			})
   241  		}
   242  	}
   243  	// Cross check that the two tries are in sync
   244  	checkTrieContents(t, triedb, srcTrie.Hash().Bytes(), srcData)
   245  }
   246  
   247  // Tests that given a root hash, a trie can sync iteratively on a single thread,
   248  // requesting retrieval tasks and returning all of them in one go, however in a
   249  // random order.
   250  func TestIterativeRandomSyncIndividual(t *testing.T) { testIterativeRandomSync(t, 1) }
   251  func TestIterativeRandomSyncBatched(t *testing.T)    { testIterativeRandomSync(t, 100) }
   252  
   253  func testIterativeRandomSync(t *testing.T, count int) {
   254  	// Create a random trie to copy
   255  	srcDb, srcTrie, srcData := makeTestTrie()
   256  
   257  	// Create a destination trie and sync with the scheduler
   258  	diskdb := memorydb.New()
   259  	triedb := NewDatabase(diskdb)
   260  	sched := NewSync(srcTrie.Hash(), diskdb, nil)
   261  
   262  	// The code requests are ignored here since there is no code
   263  	// at the testing trie.
   264  	paths, nodes, _ := sched.Missing(count)
   265  	queue := make(map[string]trieElement)
   266  	for i, path := range paths {
   267  		queue[path] = trieElement{
   268  			path:     paths[i],
   269  			hash:     nodes[i],
   270  			syncPath: NewSyncPath([]byte(paths[i])),
   271  		}
   272  	}
   273  	for len(queue) > 0 {
   274  		// Fetch all the queued nodes in a random order
   275  		results := make([]NodeSyncResult, 0, len(queue))
   276  		for path, element := range queue {
   277  			data, err := srcDb.Node(element.hash)
   278  			if err != nil {
   279  				t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err)
   280  			}
   281  			results = append(results, NodeSyncResult{path, data})
   282  		}
   283  		// Feed the retrieved results back and queue new tasks
   284  		for _, result := range results {
   285  			if err := sched.ProcessNode(result); err != nil {
   286  				t.Fatalf("failed to process result %v", err)
   287  			}
   288  		}
   289  		batch := diskdb.NewBatch()
   290  		if err := sched.Commit(batch); err != nil {
   291  			t.Fatalf("failed to commit data: %v", err)
   292  		}
   293  		batch.Write()
   294  
   295  		paths, nodes, _ = sched.Missing(count)
   296  		queue = make(map[string]trieElement)
   297  		for i, path := range paths {
   298  			queue[path] = trieElement{
   299  				path:     path,
   300  				hash:     nodes[i],
   301  				syncPath: NewSyncPath([]byte(path)),
   302  			}
   303  		}
   304  	}
   305  	// Cross check that the two tries are in sync
   306  	checkTrieContents(t, triedb, srcTrie.Hash().Bytes(), srcData)
   307  }
   308  
   309  // Tests that the trie scheduler can correctly reconstruct the state even if only
   310  // partial results are returned (Even those randomly), others sent only later.
   311  func TestIterativeRandomDelayedSync(t *testing.T) {
   312  	// Create a random trie to copy
   313  	srcDb, srcTrie, srcData := makeTestTrie()
   314  
   315  	// Create a destination trie and sync with the scheduler
   316  	diskdb := memorydb.New()
   317  	triedb := NewDatabase(diskdb)
   318  	sched := NewSync(srcTrie.Hash(), diskdb, nil)
   319  
   320  	// The code requests are ignored here since there is no code
   321  	// at the testing trie.
   322  	paths, nodes, _ := sched.Missing(10000)
   323  	queue := make(map[string]trieElement)
   324  	for i, path := range paths {
   325  		queue[path] = trieElement{
   326  			path:     path,
   327  			hash:     nodes[i],
   328  			syncPath: NewSyncPath([]byte(path)),
   329  		}
   330  	}
   331  	for len(queue) > 0 {
   332  		// Sync only half of the scheduled nodes, even those in random order
   333  		results := make([]NodeSyncResult, 0, len(queue)/2+1)
   334  		for path, element := range queue {
   335  			data, err := srcDb.Node(element.hash)
   336  			if err != nil {
   337  				t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err)
   338  			}
   339  			results = append(results, NodeSyncResult{path, data})
   340  
   341  			if len(results) >= cap(results) {
   342  				break
   343  			}
   344  		}
   345  		// Feed the retrieved results back and queue new tasks
   346  		for _, result := range results {
   347  			if err := sched.ProcessNode(result); err != nil {
   348  				t.Fatalf("failed to process result %v", err)
   349  			}
   350  		}
   351  		batch := diskdb.NewBatch()
   352  		if err := sched.Commit(batch); err != nil {
   353  			t.Fatalf("failed to commit data: %v", err)
   354  		}
   355  		batch.Write()
   356  		for _, result := range results {
   357  			delete(queue, result.Path)
   358  		}
   359  		paths, nodes, _ = sched.Missing(10000)
   360  		for i, path := range paths {
   361  			queue[path] = trieElement{
   362  				path:     path,
   363  				hash:     nodes[i],
   364  				syncPath: NewSyncPath([]byte(path)),
   365  			}
   366  		}
   367  	}
   368  	// Cross check that the two tries are in sync
   369  	checkTrieContents(t, triedb, srcTrie.Hash().Bytes(), srcData)
   370  }
   371  
   372  // Tests that a trie sync will not request nodes multiple times, even if they
   373  // have such references.
   374  func TestDuplicateAvoidanceSync(t *testing.T) {
   375  	// Create a random trie to copy
   376  	srcDb, srcTrie, srcData := makeTestTrie()
   377  
   378  	// Create a destination trie and sync with the scheduler
   379  	diskdb := memorydb.New()
   380  	triedb := NewDatabase(diskdb)
   381  	sched := NewSync(srcTrie.Hash(), diskdb, nil)
   382  
   383  	// The code requests are ignored here since there is no code
   384  	// at the testing trie.
   385  	paths, nodes, _ := sched.Missing(0)
   386  	var elements []trieElement
   387  	for i := 0; i < len(paths); i++ {
   388  		elements = append(elements, trieElement{
   389  			path:     paths[i],
   390  			hash:     nodes[i],
   391  			syncPath: NewSyncPath([]byte(paths[i])),
   392  		})
   393  	}
   394  	requested := make(map[common.Hash]struct{})
   395  
   396  	for len(elements) > 0 {
   397  		results := make([]NodeSyncResult, len(elements))
   398  		for i, element := range elements {
   399  			data, err := srcDb.Node(element.hash)
   400  			if err != nil {
   401  				t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err)
   402  			}
   403  			if _, ok := requested[element.hash]; ok {
   404  				t.Errorf("hash %x already requested once", element.hash)
   405  			}
   406  			requested[element.hash] = struct{}{}
   407  
   408  			results[i] = NodeSyncResult{element.path, data}
   409  		}
   410  		for _, result := range results {
   411  			if err := sched.ProcessNode(result); err != nil {
   412  				t.Fatalf("failed to process result %v", err)
   413  			}
   414  		}
   415  		batch := diskdb.NewBatch()
   416  		if err := sched.Commit(batch); err != nil {
   417  			t.Fatalf("failed to commit data: %v", err)
   418  		}
   419  		batch.Write()
   420  
   421  		paths, nodes, _ = sched.Missing(0)
   422  		elements = elements[:0]
   423  		for i := 0; i < len(paths); i++ {
   424  			elements = append(elements, trieElement{
   425  				path:     paths[i],
   426  				hash:     nodes[i],
   427  				syncPath: NewSyncPath([]byte(paths[i])),
   428  			})
   429  		}
   430  	}
   431  	// Cross check that the two tries are in sync
   432  	checkTrieContents(t, triedb, srcTrie.Hash().Bytes(), srcData)
   433  }
   434  
   435  // Tests that at any point in time during a sync, only complete sub-tries are in
   436  // the database.
   437  func TestIncompleteSync(t *testing.T) {
   438  	// Create a random trie to copy
   439  	srcDb, srcTrie, _ := makeTestTrie()
   440  
   441  	// Create a destination trie and sync with the scheduler
   442  	diskdb := memorydb.New()
   443  	triedb := NewDatabase(diskdb)
   444  	sched := NewSync(srcTrie.Hash(), diskdb, nil)
   445  
   446  	// The code requests are ignored here since there is no code
   447  	// at the testing trie.
   448  	var (
   449  		added    []common.Hash
   450  		elements []trieElement
   451  		root     = srcTrie.Hash()
   452  	)
   453  	paths, nodes, _ := sched.Missing(1)
   454  	for i := 0; i < len(paths); i++ {
   455  		elements = append(elements, trieElement{
   456  			path:     paths[i],
   457  			hash:     nodes[i],
   458  			syncPath: NewSyncPath([]byte(paths[i])),
   459  		})
   460  	}
   461  	for len(elements) > 0 {
   462  		// Fetch a batch of trie nodes
   463  		results := make([]NodeSyncResult, len(elements))
   464  		for i, element := range elements {
   465  			data, err := srcDb.Node(element.hash)
   466  			if err != nil {
   467  				t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err)
   468  			}
   469  			results[i] = NodeSyncResult{element.path, data}
   470  		}
   471  		// Process each of the trie nodes
   472  		for _, result := range results {
   473  			if err := sched.ProcessNode(result); err != nil {
   474  				t.Fatalf("failed to process result %v", err)
   475  			}
   476  		}
   477  		batch := diskdb.NewBatch()
   478  		if err := sched.Commit(batch); err != nil {
   479  			t.Fatalf("failed to commit data: %v", err)
   480  		}
   481  		batch.Write()
   482  
   483  		for _, result := range results {
   484  			hash := crypto.Keccak256Hash(result.Data)
   485  			if hash != root {
   486  				added = append(added, hash)
   487  			}
   488  			// Check that all known sub-tries in the synced trie are complete
   489  			if err := checkTrieConsistency(triedb, hash); err != nil {
   490  				t.Fatalf("trie inconsistent: %v", err)
   491  			}
   492  		}
   493  		// Fetch the next batch to retrieve
   494  		paths, nodes, _ = sched.Missing(1)
   495  		elements = elements[:0]
   496  		for i := 0; i < len(paths); i++ {
   497  			elements = append(elements, trieElement{
   498  				path:     paths[i],
   499  				hash:     nodes[i],
   500  				syncPath: NewSyncPath([]byte(paths[i])),
   501  			})
   502  		}
   503  	}
   504  	// Sanity check that removing any node from the database is detected
   505  	for _, hash := range added {
   506  		value, _ := diskdb.Get(hash.Bytes())
   507  		diskdb.Delete(hash.Bytes())
   508  		if err := checkTrieConsistency(triedb, root); err == nil {
   509  			t.Fatalf("trie inconsistency not caught, missing: %x", hash)
   510  		}
   511  		diskdb.Put(hash.Bytes(), value)
   512  	}
   513  }
   514  
   515  // Tests that trie nodes get scheduled lexicographically when having the same
   516  // depth.
   517  func TestSyncOrdering(t *testing.T) {
   518  	// Create a random trie to copy
   519  	srcDb, srcTrie, srcData := makeTestTrie()
   520  
   521  	// Create a destination trie and sync with the scheduler, tracking the requests
   522  	diskdb := memorydb.New()
   523  	triedb := NewDatabase(diskdb)
   524  	sched := NewSync(srcTrie.Hash(), diskdb, nil)
   525  
   526  	// The code requests are ignored here since there is no code
   527  	// at the testing trie.
   528  	var (
   529  		reqs     []SyncPath
   530  		elements []trieElement
   531  	)
   532  	paths, nodes, _ := sched.Missing(1)
   533  	for i := 0; i < len(paths); i++ {
   534  		elements = append(elements, trieElement{
   535  			path:     paths[i],
   536  			hash:     nodes[i],
   537  			syncPath: NewSyncPath([]byte(paths[i])),
   538  		})
   539  		reqs = append(reqs, NewSyncPath([]byte(paths[i])))
   540  	}
   541  
   542  	for len(elements) > 0 {
   543  		results := make([]NodeSyncResult, len(elements))
   544  		for i, element := range elements {
   545  			data, err := srcDb.Node(element.hash)
   546  			if err != nil {
   547  				t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err)
   548  			}
   549  			results[i] = NodeSyncResult{element.path, data}
   550  		}
   551  		for _, result := range results {
   552  			if err := sched.ProcessNode(result); err != nil {
   553  				t.Fatalf("failed to process result %v", err)
   554  			}
   555  		}
   556  		batch := diskdb.NewBatch()
   557  		if err := sched.Commit(batch); err != nil {
   558  			t.Fatalf("failed to commit data: %v", err)
   559  		}
   560  		batch.Write()
   561  
   562  		paths, nodes, _ = sched.Missing(1)
   563  		elements = elements[:0]
   564  		for i := 0; i < len(paths); i++ {
   565  			elements = append(elements, trieElement{
   566  				path:     paths[i],
   567  				hash:     nodes[i],
   568  				syncPath: NewSyncPath([]byte(paths[i])),
   569  			})
   570  			reqs = append(reqs, NewSyncPath([]byte(paths[i])))
   571  		}
   572  	}
   573  	// Cross check that the two tries are in sync
   574  	checkTrieContents(t, triedb, srcTrie.Hash().Bytes(), srcData)
   575  
   576  	// Check that the trie nodes have been requested path-ordered
   577  	for i := 0; i < len(reqs)-1; i++ {
   578  		if len(reqs[i]) > 1 || len(reqs[i+1]) > 1 {
   579  			// In the case of the trie tests, there's no storage so the tuples
   580  			// must always be single items. 2-tuples should be tested in state.
   581  			t.Errorf("Invalid request tuples: len(%v) or len(%v) > 1", reqs[i], reqs[i+1])
   582  		}
   583  		if bytes.Compare(compactToHex(reqs[i][0]), compactToHex(reqs[i+1][0])) > 0 {
   584  			t.Errorf("Invalid request order: %v before %v", compactToHex(reqs[i][0]), compactToHex(reqs[i+1][0]))
   585  		}
   586  	}
   587  }