github.com/dim4egster/coreth@v0.10.2/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/dim4egster/coreth/plugin/evm/message"
    13  	"github.com/dim4egster/coreth/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  	NodeType() message.NodeType        // Specifies the message type (atomic/state trie) for the leaf syncer to send
    36  	OnStart() (bool, error)            // Callback when tasks begins, returns true if work can be skipped
    37  	OnLeafs(keys, vals [][]byte) error // Callback when new leaves are received from the network
    38  	OnFinish() error                   // Callback when there are no more leaves in the trie to sync or when we reach End()
    39  }
    40  
    41  type CallbackLeafSyncer struct {
    42  	client LeafClient
    43  	done   chan error
    44  	tasks  <-chan LeafSyncTask
    45  }
    46  
    47  type LeafClient interface {
    48  	// GetLeafs synchronously sends the given request, returning a parsed LeafsResponse or error
    49  	// Note: this verifies the response including the range proofs.
    50  	GetLeafs(context.Context, message.LeafsRequest) (message.LeafsResponse, error)
    51  }
    52  
    53  // NewCallbackLeafSyncer creates a new syncer object to perform leaf sync of tries.
    54  func NewCallbackLeafSyncer(client LeafClient, tasks <-chan LeafSyncTask) *CallbackLeafSyncer {
    55  	return &CallbackLeafSyncer{
    56  		client: client,
    57  		done:   make(chan error),
    58  		tasks:  tasks,
    59  	}
    60  }
    61  
    62  // workerLoop reads from [c.tasks] and calls [c.syncTask] until [ctx] is finished
    63  // or [c.tasks] is closed.
    64  func (c *CallbackLeafSyncer) workerLoop(ctx context.Context) error {
    65  	for {
    66  		select {
    67  		case task, more := <-c.tasks:
    68  			if !more {
    69  				return nil
    70  			}
    71  			if err := c.syncTask(ctx, task); err != nil {
    72  				return err
    73  			}
    74  		case <-ctx.Done():
    75  			return ctx.Err()
    76  		}
    77  	}
    78  }
    79  
    80  // syncTask performs [task], requesting the leaves of the trie corresponding to [task.Root]
    81  // starting at [task.Start] and invoking the callbacks as necessary.
    82  func (c *CallbackLeafSyncer) syncTask(ctx context.Context, task LeafSyncTask) error {
    83  	var (
    84  		root  = task.Root()
    85  		start = task.Start()
    86  	)
    87  
    88  	if skip, err := task.OnStart(); err != nil {
    89  		return err
    90  	} else if skip {
    91  		return nil
    92  	}
    93  
    94  	for {
    95  		// If [ctx] has finished, return early.
    96  		if err := ctx.Err(); err != nil {
    97  			return err
    98  		}
    99  
   100  		leafsResponse, err := c.client.GetLeafs(ctx, message.LeafsRequest{
   101  			Root:     root,
   102  			Account:  task.Account(),
   103  			Start:    start,
   104  			Limit:    defaultLeafRequestLimit,
   105  			NodeType: task.NodeType(),
   106  		})
   107  		if err != nil {
   108  			return fmt.Errorf("%s: %w", errFailedToFetchLeafs, err)
   109  		}
   110  
   111  		// resize [leafsResponse.Keys] and [leafsResponse.Vals] in case
   112  		// the response includes any keys past [End()].
   113  		// Note: We truncate the response here as opposed to sending End
   114  		// in the request, as [VerifyRangeProof] does not handle empty
   115  		// responses correctly with a non-empty end key for the range.
   116  		done := false
   117  		if task.End() != nil && len(leafsResponse.Keys) > 0 {
   118  			i := len(leafsResponse.Keys) - 1
   119  			for ; i >= 0; i-- {
   120  				if bytes.Compare(leafsResponse.Keys[i], task.End()) <= 0 {
   121  					break
   122  				}
   123  				done = true
   124  			}
   125  			leafsResponse.Keys = leafsResponse.Keys[:i+1]
   126  			leafsResponse.Vals = leafsResponse.Vals[:i+1]
   127  		}
   128  
   129  		if err := task.OnLeafs(leafsResponse.Keys, leafsResponse.Vals); err != nil {
   130  			return err
   131  		}
   132  
   133  		// If we have completed syncing this task, invoke [OnFinish] and mark the task
   134  		// as complete.
   135  		if done || !leafsResponse.More {
   136  			return task.OnFinish()
   137  		}
   138  
   139  		if len(leafsResponse.Keys) == 0 {
   140  			return fmt.Errorf("found no keys in a response with more set to true")
   141  		}
   142  		// Update start to be one bit past the last returned key for the next request.
   143  		// Note: since more was true, this cannot cause an overflow.
   144  		start = leafsResponse.Keys[len(leafsResponse.Keys)-1]
   145  		utils.IncrOne(start)
   146  	}
   147  }
   148  
   149  // Start launches [numThreads] worker goroutines to process LeafSyncTasks from [c.tasks].
   150  // onFailure is called if the sync completes with an error.
   151  func (c *CallbackLeafSyncer) Start(ctx context.Context, numThreads int, onFailure func(error) error) {
   152  	// Start the worker threads with the desired context.
   153  	eg, egCtx := errgroup.WithContext(ctx)
   154  	for i := 0; i < numThreads; i++ {
   155  		eg.Go(func() error {
   156  			return c.workerLoop(egCtx)
   157  		})
   158  	}
   159  
   160  	go func() {
   161  		err := eg.Wait()
   162  		if err != nil {
   163  			if err := onFailure(err); err != nil {
   164  				log.Error("error handling onFailure callback", "err", err)
   165  			}
   166  		}
   167  		c.done <- err
   168  		close(c.done)
   169  	}()
   170  }
   171  
   172  // Done returns a channel which produces any error that occurred during syncing or nil on success.
   173  func (c *CallbackLeafSyncer) Done() <-chan error { return c.done }