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 }