github.com/MetalBlockchain/subnet-evm@v0.4.9/sync/client/leaf_syncer.go (about)

     1  // (c) 2021-2022, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package statesyncclient
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  
    12  	"github.com/MetalBlockchain/subnet-evm/plugin/evm/message"
    13  	"github.com/MetalBlockchain/subnet-evm/utils"
    14  	"github.com/ethereum/go-ethereum/common"
    15  	"github.com/ethereum/go-ethereum/log"
    16  	"golang.org/x/sync/errgroup"
    17  )
    18  
    19  var (
    20  	errFailedToFetchLeafs = errors.New("failed to fetch leafs")
    21  )
    22  
    23  const defaultLeafRequestLimit = 1024
    24  
    25  // LeafSyncTask represents a complete task to be completed by the leaf syncer.
    26  // Note: each LeafSyncTask is processed on its own goroutine and there will
    27  // not be concurrent calls to the callback methods. Implementations should return
    28  // the same value for Root, Account, Start, and NodeType throughout the sync.
    29  // The value returned by End can change between calls to OnLeafs.
    30  type LeafSyncTask interface {
    31  	Root() common.Hash                 // Root of the trie to sync
    32  	Account() common.Hash              // Account hash of the trie to sync (only applicable to storage tries)
    33  	Start() []byte                     // Starting key to request new leaves
    34  	End() []byte                       // End key to request new leaves
    35  	OnStart() (bool, error)            // Callback when tasks begins, returns true if work can be skipped
    36  	OnLeafs(keys, vals [][]byte) error // Callback when new leaves are received from the network
    37  	OnFinish() error                   // Callback when there are no more leaves in the trie to sync or when we reach End()
    38  }
    39  
    40  type CallbackLeafSyncer struct {
    41  	client LeafClient
    42  	done   chan error
    43  	tasks  <-chan LeafSyncTask
    44  }
    45  
    46  type LeafClient interface {
    47  	// GetLeafs synchronously sends the given request, returning a parsed LeafsResponse or error
    48  	// Note: this verifies the response including the range proofs.
    49  	GetLeafs(context.Context, message.LeafsRequest) (message.LeafsResponse, error)
    50  }
    51  
    52  // NewCallbackLeafSyncer creates a new syncer object to perform leaf sync of tries.
    53  func NewCallbackLeafSyncer(client LeafClient, tasks <-chan LeafSyncTask) *CallbackLeafSyncer {
    54  	return &CallbackLeafSyncer{
    55  		client: client,
    56  		done:   make(chan error),
    57  		tasks:  tasks,
    58  	}
    59  }
    60  
    61  // workerLoop reads from [c.tasks] and calls [c.syncTask] until [ctx] is finished
    62  // or [c.tasks] is closed.
    63  func (c *CallbackLeafSyncer) workerLoop(ctx context.Context) error {
    64  	for {
    65  		select {
    66  		case task, more := <-c.tasks:
    67  			if !more {
    68  				return nil
    69  			}
    70  			if err := c.syncTask(ctx, task); err != nil {
    71  				return err
    72  			}
    73  		case <-ctx.Done():
    74  			return ctx.Err()
    75  		}
    76  	}
    77  }
    78  
    79  // syncTask performs [task], requesting the leaves of the trie corresponding to [task.Root]
    80  // starting at [task.Start] and invoking the callbacks as necessary.
    81  func (c *CallbackLeafSyncer) syncTask(ctx context.Context, task LeafSyncTask) error {
    82  	var (
    83  		root  = task.Root()
    84  		start = task.Start()
    85  	)
    86  
    87  	if skip, err := task.OnStart(); err != nil {
    88  		return err
    89  	} else if skip {
    90  		return nil
    91  	}
    92  
    93  	for {
    94  		// If [ctx] has finished, return early.
    95  		if err := ctx.Err(); err != nil {
    96  			return err
    97  		}
    98  
    99  		leafsResponse, err := c.client.GetLeafs(ctx, message.LeafsRequest{
   100  			Root:    root,
   101  			Account: task.Account(),
   102  			Start:   start,
   103  			Limit:   defaultLeafRequestLimit,
   104  		})
   105  		if err != nil {
   106  			return fmt.Errorf("%s: %w", errFailedToFetchLeafs, err)
   107  		}
   108  
   109  		// resize [leafsResponse.Keys] and [leafsResponse.Vals] in case
   110  		// the response includes any keys past [End()].
   111  		// Note: We truncate the response here as opposed to sending End
   112  		// in the request, as [VerifyRangeProof] does not handle empty
   113  		// responses correctly with a non-empty end key for the range.
   114  		done := false
   115  		if task.End() != nil && len(leafsResponse.Keys) > 0 {
   116  			i := len(leafsResponse.Keys) - 1
   117  			for ; i >= 0; i-- {
   118  				if bytes.Compare(leafsResponse.Keys[i], task.End()) <= 0 {
   119  					break
   120  				}
   121  				done = true
   122  			}
   123  			leafsResponse.Keys = leafsResponse.Keys[:i+1]
   124  			leafsResponse.Vals = leafsResponse.Vals[:i+1]
   125  		}
   126  
   127  		if err := task.OnLeafs(leafsResponse.Keys, leafsResponse.Vals); err != nil {
   128  			return err
   129  		}
   130  
   131  		// If we have completed syncing this task, invoke [OnFinish] and mark the task
   132  		// as complete.
   133  		if done || !leafsResponse.More {
   134  			return task.OnFinish()
   135  		}
   136  
   137  		if len(leafsResponse.Keys) == 0 {
   138  			return fmt.Errorf("found no keys in a response with more set to true")
   139  		}
   140  		// Update start to be one bit past the last returned key for the next request.
   141  		// Note: since more was true, this cannot cause an overflow.
   142  		start = leafsResponse.Keys[len(leafsResponse.Keys)-1]
   143  		utils.IncrOne(start)
   144  	}
   145  }
   146  
   147  // Start launches [numThreads] worker goroutines to process LeafSyncTasks from [c.tasks].
   148  // onFailure is called if the sync completes with an error.
   149  func (c *CallbackLeafSyncer) Start(ctx context.Context, numThreads int, onFailure func(error) error) {
   150  	// Start the worker threads with the desired context.
   151  	eg, egCtx := errgroup.WithContext(ctx)
   152  	for i := 0; i < numThreads; i++ {
   153  		eg.Go(func() error {
   154  			return c.workerLoop(egCtx)
   155  		})
   156  	}
   157  
   158  	go func() {
   159  		err := eg.Wait()
   160  		if err != nil {
   161  			if err := onFailure(err); err != nil {
   162  				log.Error("error handling onFailure callback", "err", err)
   163  			}
   164  		}
   165  		c.done <- err
   166  		close(c.done)
   167  	}()
   168  }
   169  
   170  // Done returns a channel which produces any error that occurred during syncing or nil on success.
   171  func (c *CallbackLeafSyncer) Done() <-chan error { return c.done }