diff --git a/cmd/chain-newheads/main.go b/cmd/chain-newheads/main.go new file mode 100644 index 00000000..fd696205 --- /dev/null +++ b/cmd/chain-newheads/main.go @@ -0,0 +1,134 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/0xsequence/ethkit/go-ethereum/common/hexutil" + "github.com/0xsequence/ethkit/go-ethereum/rpc" + "github.com/0xsequence/ethkit/util" +) + +var ( + ETH_NODE_URL = "" + ETH_NODE_WSS_URL = "" +) + +func init() { + testConfig, err := util.ReadTestConfig("../../ethkit-test.json") + if err != nil { + panic(err) + } + + if testConfig["POLYGON_MAINNET_WSS_URL"] != "" { + ETH_NODE_URL = testConfig["POLYGON_MAINNET_URL"] + ETH_NODE_WSS_URL = testConfig["POLYGON_MAINNET_WSS_URL"] + } + // if testConfig["MAINNET_URL"] != "" { + // ETH_NODE_URL = testConfig["MAINNET_URL"] + // ETH_NODE_WSS_URL = testConfig["MAINNET_WSS_URL"] + // } + if testConfig["ARB_NOVA_WSS_URL"] != "" { + ETH_NODE_URL = testConfig["ARB_NOVA_URL"] + ETH_NODE_WSS_URL = testConfig["ARB_NOVA_WSS_URL"] + } + // if testConfig["AVAX_MAINNET_WSS_URL"] != "" { + // // ETH_NODE_URL = testConfig["ARB_NOVA_URL"] + // ETH_NODE_WSS_URL = testConfig["AVAX_MAINNET_WSS_URL"] + // } +} + +func main() { + client, err := rpc.Dial(ETH_NODE_WSS_URL) + if err != nil { + log.Fatal(err) + } + + ch := make(chan map[string]interface{}) + + sub, err := client.EthSubscribe(context.Background(), ch, "newHeads") + if err != nil { + log.Fatal(err) + } + + var prevHash string + go func() { + for { + select { + + case err := <-sub.Err(): + fmt.Println("sub err!", err) + os.Exit(1) + + case out := <-ch: + // fmt.Println("===> out:", out) + // spew.Dump(out) + + hash, ok := out["hash"].(string) + if !ok { + panic(ok) + } + parentHash, ok := out["parentHash"].(string) + if !ok { + panic(ok) + } + if prevHash != "" { + if prevHash != parentHash { + fmt.Println("REORG!") + } + } + prevHash = hash + num, ok := out["number"].(string) + if !ok { + panic("hmm") + } + blockNumber := hexutil.MustDecodeBig(num) + fmt.Println("hash", hash, "num", blockNumber.String()) + } + } + }() + + time.Sleep(20 * time.Minute) + sub.Unsubscribe() + + // os.Exit(1) + + // filter := map[string]interface{}{ + // "topics": []string{}, + // // "fromBlock": "latest", + // } + + // sub, err = client.EthSubscribe(context.Background(), ch, "logs", filter) + // if err != nil { + // log.Fatal(err) + // } + + // go func() { + // for { + // select { + + // case err := <-sub.Err(): + // fmt.Println("sub err!", err) + // os.Exit(1) + + // case out := <-ch: + // // fmt.Println("===> out:", out) + // spew.Dump(out) + + // removed, ok := out["removed"].(bool) + // if !ok { + // panic("no") + // } + // if removed { + // panic("removed!!!") + // } + // } + // } + // }() + + // time.Sleep(2 * time.Minute) + // sub.Unsubscribe() +} diff --git a/cmd/chain-receipts/main.go b/cmd/chain-receipts/main.go index 3c9c2af0..92d77721 100644 --- a/cmd/chain-receipts/main.go +++ b/cmd/chain-receipts/main.go @@ -20,6 +20,7 @@ import ( ) var ETH_NODE_URL = "http://localhost:8545" +var ETH_NODE_WSS_URL = "" func init() { testConfig, err := util.ReadTestConfig("../../ethkit-test.json") @@ -29,9 +30,11 @@ func init() { if testConfig["POLYGON_MAINNET_URL"] != "" { ETH_NODE_URL = testConfig["POLYGON_MAINNET_URL"] + ETH_NODE_WSS_URL = testConfig["POLYGON_MAINNET_WSS_URL"] } // if testConfig["MAINNET_URL"] != "" { // ETH_NODE_URL = testConfig["MAINNET_URL"] + // ETH_NODE_WSS_URL = testConfig["MAINNET_WSS_URL"] // } } @@ -39,7 +42,7 @@ func main() { fmt.Println("chain-receipts start") // Provider - provider, err := ethrpc.NewProvider(ETH_NODE_URL) + provider, err := ethrpc.NewProvider(ETH_NODE_URL, ethrpc.WithStreaming(ETH_NODE_WSS_URL)) if err != nil { log.Fatal(err) } diff --git a/cmd/chain-watch/main.go b/cmd/chain-watch/main.go index cd5630f4..7160a7e4 100644 --- a/cmd/chain-watch/main.go +++ b/cmd/chain-watch/main.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "log" + "math/big" "os" "path/filepath" "sync" @@ -20,6 +21,7 @@ import ( ) var ETH_NODE_URL = "http://localhost:8887/polygon" +var ETH_NODE_WSS_URL = "" const SNAPSHOT_ENABLED = false @@ -33,17 +35,23 @@ func init() { if testConfig["POLYGON_MAINNET_URL"] != "" { ETH_NODE_URL = testConfig["POLYGON_MAINNET_URL"] + ETH_NODE_WSS_URL = testConfig["POLYGON_MAINNET_WSS_URL"] } // if testConfig["MAINNET_URL"] != "" { // ETH_NODE_URL = testConfig["MAINNET_URL"] // } + if testConfig["ARB_NOVA_URL"] != "" { + ETH_NODE_URL = testConfig["ARB_NOVA_URL"] + ETH_NODE_WSS_URL = testConfig["ARB_NOVA_WSS_URL"] + } } func main() { fmt.Println("chain-watch start") // Provider - provider, err := ethrpc.NewProvider(ETH_NODE_URL) + // provider, err := ethrpc.NewProvider(ETH_NODE_URL) + provider, err := ethrpc.NewProvider(ETH_NODE_URL, ethrpc.WithStreaming(ETH_NODE_WSS_URL)) if err != nil { log.Fatal(err) } @@ -57,7 +65,14 @@ func main() { monitorOptions.PollingInterval = time.Duration(2000 * time.Millisecond) monitorOptions.WithLogs = true monitorOptions.BlockRetentionLimit = 64 - monitorOptions.StartBlockNumber = nil // track the head + // monitorOptions.StartBlockNumber = nil // track the head + + latestBlock, err := provider.BlockByNumber(context.Background(), nil) + if err != nil { + panic(err) + } + + monitorOptions.StartBlockNumber = big.NewInt(0).Sub(latestBlock.Number(), big.NewInt(10)) // monitorOptions.StartBlockNumber = big.NewInt(47496451) // monitorOptions.Bootstrap = true diff --git a/ethmonitor/README.md b/ethmonitor/README.md deleted file mode 100644 index ae90c639..00000000 --- a/ethmonitor/README.md +++ /dev/null @@ -1,4 +0,0 @@ -ethmonitor -========== - -inspired by the work of the 0x team at github.com/0xProject/0x-mesh diff --git a/ethmonitor/ethmonitor.go b/ethmonitor/ethmonitor.go index b761e488..a081427b 100644 --- a/ethmonitor/ethmonitor.go +++ b/ethmonitor/ethmonitor.go @@ -57,7 +57,7 @@ type Options struct { StartBlockNumber *big.Int // Bootstrap flag which indicates the monitor will expect the monitor's - // events to be bootstrapped, and will continue from that point. This als + // events to be bootstrapped, and will continue from that point. This also // takes precedence over StartBlockNumber when set to true. Bootstrap bool @@ -79,8 +79,8 @@ type Options struct { LogTopics []common.Hash // CacheBackend to use for caching block data - // NOTE: do not use this unless you know what you're doing. In most cases - // leave this nil. + // NOTE: do not use this unless you know what you're doing. + // In most cases leave this nil. CacheBackend cachestore.Backend // CacheExpiry is how long to keep each record in cache @@ -110,9 +110,11 @@ type Monitor struct { alert util.Alerter provider ethrpc.RawInterface - chain *Chain - chainID *big.Int - nextBlockNumber *big.Int + chain *Chain + chainID *big.Int + nextBlockNumber *big.Int + nextBlockNumberMu sync.Mutex + pollInterval atomic.Int64 cache cachestore.Store[[]byte] @@ -270,6 +272,96 @@ func (m *Monitor) Provider() ethrpc.Interface { return m.provider } +func (m *Monitor) listenNewHead() <-chan uint64 { + ch := make(chan uint64) + + var latestHeadBlock atomic.Uint64 + nextBlock := make(chan uint64) + + if m.provider.IsStreamingEnabled() { + // Streaming mode if available, where we listen for new heads + // and push the new block number to the nextBlock channel. + go func() { + reconnect: + newHeads := make(chan *types.Header) + sub, err := m.provider.SubscribeNewHeads(m.ctx, newHeads) + if err != nil { + m.log.Warnf("ethmonitor: websocket connect failed: %v", err) + m.alert.Alert(context.Background(), "ethmonitor: websocket connect failed", err) + time.Sleep(1500 * time.Millisecond) + goto reconnect + } + + for { + select { + case <-m.ctx.Done(): + sub.Unsubscribe() + close(nextBlock) + return + case err := <-sub.Err(): + m.log.Warnf("ethmonitor: websocket subscription error: %v", err) + m.alert.Alert(context.Background(), "ethmonitor: websocket subscription error", err) + sub.Unsubscribe() + goto reconnect + + case newHead := <-newHeads: + latestHeadBlock.Store(newHead.Number.Uint64()) + select { + case nextBlock <- newHead.Number.Uint64(): + default: + // non-blocking + } + } + } + }() + } else { + // We default to polling if streaming is not enabled + go func() { + for { + select { + case <-m.ctx.Done(): + close(nextBlock) + return + case <-time.After(time.Duration(m.pollInterval.Load())): + nextBlock <- 0 + } + } + }() + } + + // The main loop which notifies the monitor to continue to the next block + go func() { + for { + select { + case <-m.ctx.Done(): + return + default: + } + + var nextBlockNumber uint64 + m.nextBlockNumberMu.Lock() + if m.nextBlockNumber != nil { + nextBlockNumber = m.nextBlockNumber.Uint64() + } + m.nextBlockNumberMu.Unlock() + + latestBlockNum := latestHeadBlock.Load() + if nextBlockNumber == 0 || latestBlockNum > nextBlockNumber { + // monitor is behind, so we just push to keep going without + // waiting on the nextBlock channel + ch <- nextBlockNumber + continue + } else { + // wait for the next block + <-nextBlock + ch <- latestBlockNum + } + } + }() + + return ch +} + func (m *Monitor) monitor() error { ctx := m.ctx events := Blocks{} @@ -279,8 +371,8 @@ func (m *Monitor) monitor() error { // will actually use the poll interval while searching for the next block. minLoopInterval := 5 * time.Millisecond - // adaptive poll interval - pollInterval := m.options.PollingInterval + // listen for new heads either via streaming or polling + listenNewHead := m.listenNewHead() // monitor run loop for { @@ -289,14 +381,24 @@ func (m *Monitor) monitor() error { case <-m.ctx.Done(): return nil - case <-time.After(pollInterval): - // ... + case newHeadNum := <-listenNewHead: + // ensure we + m.nextBlockNumberMu.Lock() + if m.nextBlockNumber != nil && newHeadNum > 0 && m.nextBlockNumber.Uint64() > newHeadNum { + m.nextBlockNumberMu.Unlock() + continue + } + m.nextBlockNumberMu.Unlock() + + // check if we have a head block, if not, then we set the nextBlockNumber headBlock := m.chain.Head() if headBlock != nil { + m.nextBlockNumberMu.Lock() m.nextBlockNumber = big.NewInt(0).Add(headBlock.Number(), big.NewInt(1)) + m.nextBlockNumberMu.Unlock() } - // .. + // fetch the next block, either via the stream or via a poll nextBlock, nextBlockPayload, miss, err := m.fetchNextBlock(ctx) if err != nil { if errors.Is(err, context.DeadlineExceeded) { @@ -313,9 +415,9 @@ func (m *Monitor) monitor() error { // if we hit a miss between calls, then we reset the pollInterval, otherwise // we speed up the polling interval if miss { - pollInterval = m.options.PollingInterval + m.pollInterval.Store(int64(m.options.PollingInterval)) } else { - pollInterval = clampDuration(minLoopInterval, pollInterval/4) + m.pollInterval.Store(int64(clampDuration(minLoopInterval, time.Duration(m.pollInterval.Load())/4))) } // build deterministic set of add/remove events which construct the canonical chain @@ -572,7 +674,12 @@ func (m *Monitor) fetchNextBlock(ctx context.Context) (*types.Block, []byte, boo nextBlockPayload, err := m.fetchRawBlockByNumber(ctx, m.nextBlockNumber) if errors.Is(err, ethereum.NotFound) { miss = true - time.Sleep(m.options.PollingInterval) + if m.provider.IsStreamingEnabled() { + // in streaming mode, we'll use a shorter time to pause before we refetch + time.Sleep(200 * time.Millisecond) + } else { + time.Sleep(m.options.PollingInterval) + } continue } if err != nil { @@ -586,8 +693,15 @@ func (m *Monitor) fetchNextBlock(ctx context.Context) (*types.Block, []byte, boo } } + var nextBlockNumber *big.Int + m.nextBlockNumberMu.Lock() + if m.nextBlockNumber != nil { + nextBlockNumber = big.NewInt(0).Set(m.nextBlockNumber) + } + m.nextBlockNumberMu.Unlock() + // skip cache if isn't provided, or in case when nextBlockNumber is nil (latest) - if m.cache == nil || m.nextBlockNumber == nil { + if m.cache == nil || nextBlockNumber == nil { resp, err := getter(ctx, "") if err != nil { return nil, resp, miss, err @@ -597,7 +711,7 @@ func (m *Monitor) fetchNextBlock(ctx context.Context) (*types.Block, []byte, boo } // fetch with distributed mutex - key := cacheKeyBlockNum(m.chainID, m.nextBlockNumber) + key := cacheKeyBlockNum(m.chainID, nextBlockNumber) resp, err := m.cache.GetOrSetWithLockEx(ctx, key, getter, m.options.CacheExpiry) if err != nil { return nil, resp, miss, err diff --git a/ethrpc/ethrpc.go b/ethrpc/ethrpc.go index 2391304f..762fe673 100644 --- a/ethrpc/ethrpc.go +++ b/ethrpc/ethrpc.go @@ -15,6 +15,7 @@ import ( "github.com/0xsequence/ethkit/go-ethereum/accounts/abi/bind" "github.com/0xsequence/ethkit/go-ethereum/common" "github.com/0xsequence/ethkit/go-ethereum/core/types" + "github.com/0xsequence/ethkit/go-ethereum/rpc" "github.com/goware/breaker" "github.com/goware/logger" "github.com/goware/superr" @@ -23,6 +24,7 @@ import ( type Provider struct { log logger.Logger nodeURL string + nodeWSSURL string httpClient httpClient br breaker.Breaker jwtToken string // optional @@ -30,6 +32,8 @@ type Provider struct { chainID *big.Int // cache cachestore.Store[[]byte] // NOTE: unused for now lastRequestID uint64 + + gethRPC *rpc.Client } func NewProvider(nodeURL string, options ...Option) (*Provider, error) { @@ -411,7 +415,34 @@ func (p *Provider) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint6 // SubscribeFilterLogs is stubbed below so we can adhere to the bind.ContractBackend interface. func (p *Provider) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { - return nil, fmt.Errorf("ethrpc: method is unavailable") + if !p.IsStreamingEnabled() { + return nil, fmt.Errorf("ethrpc: provider instance has not enabled streaming") + } + if p.gethRPC == nil { + var err error + p.gethRPC, err = rpc.Dial(p.nodeWSSURL) + if err != nil { + return nil, fmt.Errorf("ethrpc: SubscribeFilterLogs failed: %w", err) + } + } + + return p.gethRPC.EthSubscribe(ctx, ch, "logs", query) +} + +// .. +func (p *Provider) SubscribeNewHeads(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { + if !p.IsStreamingEnabled() { + return nil, fmt.Errorf("ethrpc: provider instance has not enabled streaming") + } + if p.gethRPC == nil { + var err error + p.gethRPC, err = rpc.Dial(p.nodeWSSURL) + if err != nil { + return nil, fmt.Errorf("ethrpc: SubscribeNewHeads failed: %w", err) + } + } + + return p.gethRPC.EthSubscribe(ctx, ch, "newHeads") } // ie, ContractQuery(context.Background(), "0xabcdef..", "balanceOf(uint256)", "uint256", []string{"1"}) @@ -443,3 +474,8 @@ func (p *Provider) contractQuery(ctx context.Context, contractAddress string, in _, err = p.Do(ctx, contractQueryBuilder.Into(&result)) return result, err } + +// ... +func (p *Provider) IsStreamingEnabled() bool { + return p.nodeWSSURL != "" +} diff --git a/ethrpc/interface.go b/ethrpc/interface.go index 5f2336dd..d8dc3461 100644 --- a/ethrpc/interface.go +++ b/ethrpc/interface.go @@ -63,8 +63,11 @@ import ( // eth_submitWork // eth_submitHashrate -// TODO: rename to either Provider or Client or Adapter +// TODO: rename to either Provider, and rename the current Provider to Client type Interface interface { + // .. + Do(ctx context.Context, calls ...Call) ([]byte, error) + // ChainID = eth_chainId ChainID(ctx context.Context) (*big.Int, error) @@ -163,12 +166,20 @@ type Interface interface { // SendRawTransaction = eth_sendRawTransaction SendRawTransaction(ctx context.Context, signedTxHex string) (common.Hash, error) + + // .. + IsStreamingEnabled() bool + + // .. + SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) + + // .. + SubscribeNewHeads(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) } // RawInterface also returns the bytes of the response body payload type RawInterface interface { Interface - RawBlockByHash(ctx context.Context, hash common.Hash) (json.RawMessage, error) RawBlockByNumber(ctx context.Context, blockNum *big.Int) (json.RawMessage, error) RawFilterLogs(ctx context.Context, q ethereum.FilterQuery) (json.RawMessage, error) diff --git a/ethrpc/option.go b/ethrpc/option.go index 90120bcf..59d73d3e 100644 --- a/ethrpc/option.go +++ b/ethrpc/option.go @@ -13,6 +13,12 @@ type httpClient interface { Do(req *http.Request) (*http.Response, error) } +func WithStreaming(nodeWebsocketURL string) Option { + return func(p *Provider) { + p.nodeWSSURL = nodeWebsocketURL + } +} + func WithHTTPClient(c httpClient) Option { return func(p *Provider) { p.httpClient = c diff --git a/go.mod b/go.mod index 9e2fa259..303f9a3c 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/go-chi/httpvcr v0.2.0 github.com/go-stack/stack v1.8.0 github.com/google/uuid v1.2.0 - github.com/gorilla/websocket v1.5.0 + github.com/gorilla/websocket v1.5.1 github.com/goware/breaker v0.1.2 github.com/goware/cachestore v0.8.0 github.com/goware/calc v0.2.0 @@ -26,18 +26,18 @@ require ( github.com/spf13/cobra v1.6.1 github.com/stretchr/testify v1.8.1 github.com/tyler-smith/go-bip39 v1.1.0 - golang.org/x/crypto v0.6.0 - golang.org/x/net v0.7.0 + golang.org/x/crypto v0.21.0 + golang.org/x/net v0.22.0 golang.org/x/sync v0.1.0 - golang.org/x/sys v0.5.0 - golang.org/x/tools v0.2.0 + golang.org/x/sys v0.18.0 + golang.org/x/tools v0.6.0 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce ) require ( golang.org/x/exp v0.0.0-20230124195608-d38c7dcee874 // indirect - golang.org/x/mod v0.6.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/text v0.14.0 // indirect ) require ( @@ -51,7 +51,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/redis/go-redis/v9 v9.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/term v0.5.0 // indirect + golang.org/x/term v0.18.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 449cd9c7..f322bbc4 100644 --- a/go.sum +++ b/go.sum @@ -67,8 +67,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/goware/breaker v0.1.2 h1:er7Jo7OAUdKyN0iXBQGI2x18MiXjTKNaE4P+ceimNzE= github.com/goware/breaker v0.1.2/go.mod h1:ijCEfXAa0j6w7IoHA4v6Sox2W6U9HUbI/t+5x0zGaug= github.com/goware/cachestore v0.8.0 h1:NWW9nh7eXgDQfaxdhWOdopKRDc6bWH8qm5kv2w6LS+k= @@ -134,19 +134,19 @@ github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3C golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20230124195608-d38c7dcee874 h1:kWC3b7j6Fu09SnEBr7P4PuQyM0R6sqyH9R+EjIvT1nQ= golang.org/x/exp v0.0.0-20230124195608-d38c7dcee874/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -159,18 +159,18 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=