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 }