当前位置: 首页 > news >正文

Go Ethereum源码学习笔记 001 Geth Start

Go Ethereum源码学习笔记

  • 前言
  • [Chapter_001] 万物的起点: Geth Start
    • 什么是 geth?
      • go-ethereum Codebase 结构
    • Geth Start
      • 前奏: Geth Console
      • geth 节点是如何启动的
      • Node
        • Node的关闭
      • Ethereum Backend
      • 附录

前言

首先读者需要具备Go语言基础,至少要通关菜鸟教程,知道Go语言的基本语法,这些基础教程网络上非常多,请大家自行学习。
具备语言基础了,还需要在开始这一章之前做一些准备工作:

  1. 安装Go SDK,即Go语言的开发环境;
  2. 安装GoLand,即Go语言的IDE,当然也可以选择VSCode等其他IDE;
  3. 克隆 Go Ethereum源码;
  4. 克隆Understanding-Ethereum-Go-version源码(可选);
  5. 以太坊基础知识,Ethereum 协议(黄皮书 )
    做好这些准备工作,就可以打开 Go Ethereum源码了,如下图所示:
    以太坊Go源码
    以太坊是以区块链作为基础的应用,所以我们必须具备区块链相关的基础知识,否则很难读懂源码究竟在做什么。

侵删声明:如果本文有侵犯到Understanding-Ethereum-Go-version原作者的地方,请告知删除相关内容,笔者已经向Understanding-Ethereum-Go-version作者发送了添加微信好友的请求,希望可以加到对方好友!
本文的宗旨还是学习和分享,并无商业目的,希望可以将自己的心得记录下来,对于引用的出处都会提前声明。
下面开始对Understanding-Ethereum-Go-version的增删改查工作,当然,主要是跟着作者思路去学习!

[Chapter_001] 万物的起点: Geth Start

本章概要:

  1. go-ethereum 代码库的主要目录结构。
  2. geth 客户端/节点是如何启动的。
  3. 如何修改/添加 geth 对外的APIs。

什么是 geth?

geth 是以太坊基金会基于 Go 语言开发以太坊的官方客户端,它实现了 Ethereum 协议(黄皮书 )中所有需要的实现的功能模块。我们可以通过启动 geth 来运行一个 Ethereum 的节点。在以太坊 Merge 之后,geth 作为节点的执行层继续在以太坊生态中发挥重要的作用。 go-ethereum是包含了 geth 客户端代码和以及编译 geth 所需要的其他代码在内的一个完整的代码库。在本系列中我们会通过深入 go-ethereum 代码库,从High-level 的 API 接口出发,沿着 Ethereum 主 Workflow,逐一的理解 Ethereum 具体实现的细节。

为了方便区分,在接下来的文章中,我们用 geth 来表示 Geth 客户端程序,用 go-ethereum (Geth)来表示 go-ethereum 的代码库。

总结的来说:

  1. 基于 go-ethereum 代码库中的代码,我们可以编译出 geth 客户端程序。
  2. 通过运行 geth 客户端程序我们可以启动一个 Ethereum 的节点。

go-ethereum Codebase 结构

为了更好的从整体工作流的角度来理解 Ethereum,根据主要的业务功能,我们可以把 go-ethereum 划分成如下几个模块。

  • Geth Client 模块(客户端)
  • Core 数据结构模块
  • State Management 模块(状态管理)
    • StateDB 模块(状态数据库)
    • Trie 数据结构模块
    • State Optimization (Pruning)减枝算法优化
  • Mining 模块(挖矿)
  • EVM 模块(以太坊虚拟机Ethereum Virtual Machine)
  • P2P 网络模块
    • 节点数据同步
      • 交易数据
      • 区块数据
      • 区块链数据
  • Storage 模块
    • 抽象数据库层
    • LevelDB 调用

目前,go-ethereum 代码库中的主要目录结构如下所示:

cmd/ 以太坊基金会官方开发的一些 Command-line 程序。该目录下的每个子目录都是一个单独运行的 CLI 程序。|── clef/ 以太坊官方推出的账户管理程序.|── geth/ 以太坊官方的节点客户端。
core/   以太坊核心模块,包括核心数据结构,statedb,EVM 等核心数据结构以及算法实现|── rawdb/ db 相关函数的高层封装(在 ethdb 和更底层的 leveldb 之上的封装)├──accessors_state.go 从 Disk Level 读取/写入与 State 相关的数据结构。|── state/├── statedb.go  StateDB 是管理以太坊 World State 最核心的代码,用于管理链上所有的 State 相关操作。├── state_object.go state_object 是以太坊账户(包括 EOA & Contract)在 StateDB 具体的实现。|── txpool        Transaction Pool 相关的代码。|── txpool.go  Transaction Pool 的具体实现。|── types/  以太坊中最核心的数据结构|── block.go   以太坊 Block 的的数据结构定义与相关函数实现|── bloom9.go  以太坊使用的一个 Bloom Filter 的实现|── transaction.go 以太坊 Transaction 的数据结构定义与相关函数实现。|── transaction_signing.go 用于对 Transaction 进行签名的函数的实现。|── receipt.go  以太坊交易收据的实现,用于记录以太坊 Transaction 执行的结果|── vm/            以太坊的核心中核心 EVM 相关的一些的数据结构的定义。|── evm.go            EVM 数据结构和方法的定义|── instructions.go   EVM 指令的具体的定义,核心中的核心中的核心文件。|── logger.go   用于追踪 EVM 执行交易过程的日志接口的定义。具体的实现在eth/tracers/logger/logger.go 文件中。|── opcode.go   EVM 指令和数值的对应关系。|── genesis.go     创世区块相关的函数。每个 geth 客户端/以太坊节点初始化的都需要调用这个模块。|── state_processor.go EVM 执行交易的核心代码模块。 
console/|── bridge.go|── console.go  Geth Web3 控制台的入口
eth/      Ethereum 节点/后端/客户端具体功能定义和实现。例如节点的启动关闭,P2P 网络中交易和区块的同步。
ethdb/    Ethereum 本地存储的相关实现, 包括 leveldb 的调用|── leveldb/   Go-Ethereum使用的与 Bitcoin Core version一样的Leveldb作为本机存储用的数据库
internal/ 一些内部使用的工具库的集合,比如在测试用例中模拟 cmd 的工具。在构建 Ethereum 生态相关的工具时值得注意这个文件夹。
miner/|── miner.go   矿工模块的实现。|── worker.go  Block generation 的实现,包括打包 transaction,计算合法的 Block
p2p/     Ethereum 的P2P模块|── params    Ethereum 的一些参数的配置,例如: bootnode 的 enode 地址|── bootnodes.go  bootnode 的 enode 地址 like: aws 的一些节点,azure 的一些节点,Ethereum Foundation 的节点和 Rinkeby 测试网的节点
rlp/     RLP的 Encode与 Decode的相关,RLP(Recursive Length Prefix)是以太坊中序列化数据的编码方式。
rpc/     Ethereum RPC客户端的实现,远程过程调用。
les/     Ethereum light client 轻节点的实现
trie/    Ethereum 中至关重要的数据结构 Merkle Patrica Trie(MPT) 的实现|── committer.go    Trie 向 Memory Database 提交数据的工具函数。|── database.go     Memory Database,是 Trie 数据和 Disk Database 提交的中间层。同时还实现了 Trie 剪枝的功能。**非常重要**|── node.go         MPT中的节点的定义以及相关的函数。|── secure_trie.go  基于 Trie 的封装的结构。与 trie 中的函数功能相同,不过secure_trie中的 key 是经过hashKey()函数hash过的,无法通过路径获得原始的 key值 |── stack_trie.go   Block 中使用的 Transaction/Receipt Trie 的实现|── trie.go         MPT 具体功能的函数实现。

Geth Start

前奏: Geth Console

当我们想要部署一个 Ethereum 节点的时候,最直接的方式就是下载官方提供的发行版的 geth 客户端程序。geth是一个基于 CLI (命令行)的应用,启动geth和调用 geth 的功能性 API 需要使用对应的指令来操作。geth 提供了一个相对友好的 console 来方便用户调用各种指令。当我第一次阅读 Ethereum 的文档的时候,我曾经有过这样的疑问,为什么geth是由 Go 语言编写的,但是在官方文档中的 Web3 的API却是基于 Javascript 的调用?

这是因为 geth 内置了一个 Javascript 的解释器: Goja (interpreter),来作为用户与 geth 交互的 CLI Console。我们可以在console/console.go 中找到它的定义。

< !-- /Goja is an implementation of ECMAScript 5.1 in Pure GO/ -->

//控制台是一个JavaScript解释的运行时环境。它是一个完全成熟的JavaScript控制台,通过外部或进程内RPC客户端连接到正在运行的节点。
type Console struct {client   *rpc.Client         // 通过RPC客户端执行以太坊请求jsre     *jsre.JSRE          // 运行解释器的JavaScript运行时环境prompt   string              // 输入提示前缀字符串prompter prompt.UserPrompter // 输入提示器,通过它来允许交互式用户反馈histPath string              // 控制台回滚历史记录的绝对路径history  []string            // 由控制台维护的滚动历史记录字符串数组printer  io.Writer           // 输出写入器,通过它来序列化任何显示字符串interactiveStopped chan struct{}stopInteractiveCh  chan struct{}signalReceived     chan struct{}stopped            chan struct{}wg                 sync.WaitGroupstopOnce           sync.Once
}

笔者对引用的源代码做了更新,并且对注解做了中文翻译,大家阅读时可以参照源代码。

geth 节点是如何启动的

了解 Ethereum,我们首先要了解 Ethereum 客户端 Geth 是怎么运行的。 geth 程序的启动点位于 cmd/geth/main.go/main() 函数处,如下所示。

func main() {
//笔者注:运行app,如果有错误就打印出来,然后退出if err := app.Run(os.Args); err != nil {fmt.Fprintln(os.Stderr, err)os.Exit(1)}
}

笔者这里是补充一下main.go中对app的定义

var app = flags.NewApp("the go-ethereum command line interface")func init() {// Initialize the CLI app and start Gethapp.Action = gethapp.HideVersion = true // we have a command to print the versionapp.Copyright = "Copyright 2013-2022 The go-ethereum Authors"app.Commands = []*cli.Command{// See chaincmd.go:initCommand,importCommand,exportCommand,importPreimagesCommand,exportPreimagesCommand,removedbCommand,dumpCommand,dumpGenesisCommand,// See accountcmd.go:accountCommand,walletCommand,// See consolecmd.go:consoleCommand,attachCommand,javascriptCommand,// See misccmd.go:makecacheCommand,makedagCommand,versionCommand,versionCheckCommand,licenseCommand,// See config.godumpConfigCommand,// see dbcmd.godbCommand,// See cmd/utils/flags_legacy.goutils.ShowDeprecated,// See snapshot.gosnapshotCommand,// See verkle.goverkleCommand,}sort.Sort(cli.CommandsByName(app.Commands))app.Flags = flags.Merge(nodeFlags,rpcFlags,consoleFlags,debug.Flags,metricsFlags,)app.Before = func(ctx *cli.Context) error {flags.MigrateGlobalFlags(ctx)return debug.Setup(ctx)}app.After = func(ctx *cli.Context) error {debug.Exit()prompt.Stdin.Close() // Resets terminal mode.return nil}
}

大家从app的定义中知道了app就是go-ethereum命令行接口,那么才会进一步有下面的逻辑,所以大家在阅读的时候一定要参照源代码,否则很难跟上作者的节奏。

我们可以看到 main() 函数非常的简短,其主要功能就是启动一个解析 command line命令的工具: gopkg.in/urfave/cli.v1。继续深入,我们会发现在 cli app 初始化的时候会调用 app.Action = geth ,来调用 geth() 函数。而 geth() 函数就是用于启动 Ethereum 节点的顶层函数,其代码如下所示:

// 如果没有运行特殊的子命令,Geth是进入系统的主要入口点。
// 它根据命令行参数创建一个默认节点,并以阻塞模式运行它,直到它关闭才解除阻塞。
func geth(ctx *cli.Context) error {if args := ctx.Args().Slice(); len(args) > 0 {return fmt.Errorf("invalid command: %q", args[0])}prepare(ctx)stack, backend := makeFullNode(ctx)defer stack.Close()startNode(ctx, stack, backend, false)stack.Wait()return nil
}

geth() 函数中,有三个比较重要的函数调用,分别是:prepare()makeFullNode(),以及 startNode()

prepare() 函数的实现就在当前的 main.go 文件中。它主要用于设置一些节点初始化需要的配置。比如,我们在节点启动时看到的这句话: Starting Geth on Ethereum mainnet… 就是在 prepare() 函数中被打印出来的。

// prepare函数操作内存缓存空间分配并设置矩阵系统。
// 这个函数应该在启动devp2p栈之前被调用。(devp2p,dev就是开发的意思,p2p就是点到点)
func prepare(ctx *cli.Context) {// 如果我们正在运行一个已知的预设,为了方便起见记录它。switch {case ctx.IsSet(utils.RopstenFlag.Name):log.Info("Starting Geth on Ropsten testnet...")case ctx.IsSet(utils.RinkebyFlag.Name):log.Info("Starting Geth on Rinkeby testnet...")case ctx.IsSet(utils.GoerliFlag.Name):log.Info("Starting Geth on Görli testnet...")case ctx.IsSet(utils.SepoliaFlag.Name):log.Info("Starting Geth on Sepolia testnet...")case ctx.IsSet(utils.KilnFlag.Name):log.Info("Starting Geth on Kiln testnet...")case ctx.IsSet(utils.DeveloperFlag.Name):log.Info("Starting Geth in ephemeral dev mode...")log.Warn(`You are running Geth in --dev mode. Please note the following:1. 此模式仅用于快速的迭代开发,没有安全性或持久性的考虑。2. 除非另有说明,否则数据库将在内存中创建。因此,关闭计算机或断电将擦除开发环境中的整个区块数据和链状态。3. 一个随机的、预先分配的开发者账户将可用并解锁为eth.Coinbase,可用于测试。随机的dev帐户是临时的,存储在一个ramdisk硬盘上,如果你的机器重新启动,这个帐户就会丢失。4. 默认开启挖掘。但是,只有在mempool(内存池)中有待处理的事务时,客户端才会密封块。该矿工接受的最低汽油价格是1。5. 禁用网络;没有listen-address(监听地址),最大对等体数设置为0,发现功能未开启。
`)case !ctx.IsSet(utils.NetworkIdFlag.Name):log.Info("Starting Geth on Ethereum mainnet...")}// 如果我们是主网上没有指定缓存的完整节点,则取消默认缓存配额if ctx.String(utils.SyncModeFlag.Name) != "light" && !ctx.IsSet(utils.CacheFlag.Name) && !ctx.IsSet(utils.NetworkIdFlag.Name) {// 确保我们也不在任何受支持的预配置testnet测试网络上if !ctx.IsSet(utils.RopstenFlag.Name) &&!ctx.IsSet(utils.SepoliaFlag.Name) &&!ctx.IsSet(utils.RinkebyFlag.Name) &&!ctx.IsSet(utils.GoerliFlag.Name) &&!ctx.IsSet(utils.KilnFlag.Name) &&!ctx.IsSet(utils.DeveloperFlag.Name) {// 不,我们真的在主网上。提升缓存!log.Info("Bumping default cache on mainnet", "provided", ctx.Int(utils.CacheFlag.Name), "updated", 4096)ctx.Set(utils.CacheFlag.Name, strconv.Itoa(4096))}}// 如果我们在任何网络上运行轻量客户端,请将缓存降低到某个有意义的值if ctx.String(utils.SyncModeFlag.Name) == "light" && !ctx.IsSet(utils.CacheFlag.Name) {log.Info("Dropping default light client cache", "provided", ctx.Int(utils.CacheFlag.Name), "updated", 128)ctx.Set(utils.CacheFlag.Name, strconv.Itoa(128))}// 如果有启用则启动矩阵导出utils.SetupMetrics(ctx)// 启动系统运行时矩阵收集go metrics.CollectProcessMetrics(3 * time.Second)
}

prepare函数源码在原文中没有给出,因为作者预设大家会看源代码,所以比较精简,笔者在这里给出源代码是为了方便大家流畅阅读,后续的源码中如果有“笔者附加源码”的字样,代表原文没有引用的源码,但是笔者为了方便大家阅读而附加上去的,类似这样的提示后文就不再说明了。

makeFullNode() 函数的实现位于 cmd/geth/config.go 文件中。它会将 Geth 启动时的命令的上下文加载到配置中,并生成 stackbackend 这两个实例。其中 stack 是一个 Node 类型的实例,它是通过 makeFullNode() 函数调用 makeConfigNode() 函数来初始化的。Node 是 geth 生命周期中最顶级的实例,它负责管理节点中的 P2P Server, Http Server, Database 等业务非直接相关的高级抽象。关于 Node 类型的定义位于node/node.go文件中。

// 笔者附加源码
// makeFullNode加载geth配置并创建以太坊后端。
func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {stack, cfg := makeConfigNode(ctx)//这里的stack就是返回的Node节点,cfg是config配置//读取配置if ctx.IsSet(utils.OverrideTerminalTotalDifficulty.Name) {cfg.Eth.OverrideTerminalTotalDifficulty = flags.GlobalBig(ctx, utils.OverrideTerminalTotalDifficulty.Name)}if ctx.IsSet(utils.OverrideTerminalTotalDifficultyPassed.Name) {override := ctx.Bool(utils.OverrideTerminalTotalDifficultyPassed.Name)cfg.Eth.OverrideTerminalTotalDifficultyPassed = &override}//根据节点和配置注册以太坊服务backend, eth := utils.RegisterEthService(stack, &cfg.Eth)// 警告用户迁移,如果他们有一个遗留的冷冻格式。if eth != nil && !ctx.IsSet(utils.IgnoreLegacyReceiptsFlag.Name) {firstIdx := uint64(0)// 侵入以加快对主网的检查,因为我们知道第一个非空块,创世区块46147,这个编号说明以太坊为自己至少提前挖掘了46147个区块。ghash := rawdb.ReadCanonicalHash(eth.ChainDb(), 0)if cfg.Eth.NetworkId == 1 && ghash == params.MainnetGenesisHash {firstIdx = 46147}isLegacy, firstLegacy, err := dbHasLegacyReceipts(eth.ChainDb(), firstIdx)if err != nil {log.Error("Failed to check db for legacy receipts", "err", err)} else if isLegacy {stack.Close()log.Error("Database has receipts with a legacy format", "firstLegacy", firstLegacy)utils.Fatalf("Aborting. Please run `geth db freezer-migrate`.")}}// 配置日志过滤器RPC API。filterSystem := utils.RegisterFilterAPI(stack, backend, &cfg.Eth)// 如果需要,配置GraphQL。if ctx.IsSet(utils.GraphQLEnabledFlag.Name) {utils.RegisterGraphQLService(stack, backend, filterSystem, &cfg.Node)}// 如果需要,添加以太坊统计守护进程。if cfg.Ethstats.URL != "" {utils.RegisterEthStatsService(stack, backend, cfg.Ethstats.URL)}return stack, backend
}

这里的 backend 是一个 ethapi.Backend 类型的接口,提供了获取以太坊执行层运行时,所需要的基本函数功能。它的定义位于 internal/ethapi/backend.go 中。 由于这个接口中函数较多,我们选取了其中的部分关键函数方便大家理解这个接口所提供的基本功能,如下所示。

// 笔者更新源码且附注中文注解
// 后端接口提供公共API服务(由完全客户端和轻量级客户端提供)以访问必要的功能。
type Backend interface {// 以太坊通用APISyncProgress() ethereum.SyncProgress//同步进度SuggestGasTipCap(ctx context.Context) (*big.Int, error)FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error)ChainDb() ethdb.DatabaseAccountManager() *accounts.ManagerExtRPCEnabled() boolRPCGasCap() uint64            // global gas cap for eth_call over rpc: DoS protectionRPCEVMTimeout() time.Duration // global timeout for eth_call over rpc: DoS protectionRPCTxFeeCap() float64         // global tx fee cap for all transaction related APIsUnprotectedAllowed() bool     // allows only for EIP155 transactions.// 区块链APISetHead(number uint64)HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error)HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error)CurrentHeader() *types.HeaderCurrentBlock() *types.BlockBlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error)StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error)StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error)PendingBlockAndReceipts() (*types.Block, types.Receipts)GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)GetTd(ctx context.Context, hash common.Hash) *big.IntGetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error)SubscribeChainEvent(ch chan<- core.ChainEvent) event.SubscriptionSubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.SubscriptionSubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription// 交易池APISendTx(ctx context.Context, signedTx *types.Transaction) errorGetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error)GetPoolTransactions() (types.Transactions, error)GetPoolTransaction(txHash common.Hash) *types.TransactionGetPoolNonce(ctx context.Context, addr common.Address) (uint64, error)Stats() (pending int, queued int)TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions)TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions)SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.SubscriptionChainConfig() *params.ChainConfigEngine() consensus.Engine// 这里是从filters.Backend复制的// eth/filters 需要从这个后端类型初始化,所以它所需的方法也必须包含在这里。GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error)SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.SubscriptionSubscribeLogsEvent(ch chan<- []*types.Log) event.SubscriptionSubscribePendingLogsEvent(ch chan<- []*types.Log) event.SubscriptionBloomStatus() (uint64, uint64)ServiceFilter(ctx context.Context, session *bloombits.MatcherSession)
}

我们可以发现 ethapi.Backend 接口主要对外提供了:

  1. General Ethereum APIs, 这些 General APIs 对外提供了查询区块链节点管理对象的接口,例如 ChainDb() 返回当前节点的 DB 实例, AccountManager()返回账户管理对象;
  2. Blockchain 相关的 APIs, 例如链上数据的查询(Block & Transaction), CurrentHeader(), BlockByNumber(), GetTransaction();
  3. Transaction Pool (交易缓存池)相关的APIs, 例如发送交易到本节点的 Transaction Pool, 以及查询交易池中的 Transactions, GetPoolTransaction获取交易池。

目前 Geth 代码库中,有两个 ethapi.Backend 接口的实现,分别是:

  • 位于 eth\api_backend 中的 EthAPIBackend(全节点)
  • 位于 les\api_backendLesApiBackend(轻节点)

顾名思义,EthAPIBackend 提供了针对全节点的 Backend API 服务, 而 LesApiBackend 提供了轻节点的 Backend API 服务。总结的来说,如果读者想定制一些新的 RPC API(远程过程调用接口),可以在 ethapi.Backend 接口中定义函数,并给 EthAPIBackend 添加具体的实现。

读者可能会发现,ethapi.Backend 接口所提供的函数功能,主要读写本地的维护的数据结构(i.e. Transaction Pool, Blockchain)的为主。那么作为一个有网络连接的 Backend, 以太坊的 Backend 或者说 Node 是怎么管理以太坊执行层节点的网络连接,共识等功能模块的呢?

我们深入 makeFullNode() 函数可以发现,生成ethapi.Backend 接口的语句 backend, eth := utils.RegisterEthService(stack, &cfg.Eth), 还返回了另一个 Ethereum 类型的实例 eth。 这个 Ethereum 类型才是以太坊节点数结构中核心中的核心,它实现了以太坊全节点所需要的所有的 Service。它负责提供更为具体的以太坊的功能性 Service, 负责与以太坊业务直接相关的抽象,比如维护 Blockchain 的更新,共识算法,从 P2P 网络中同步区块,同步P2P节点远端的交易并放到交易池中,等业务功能。我们会在后续详细讲解 Ethereum 类型具体提供的服务。

// 笔者附加源码
// RegisterEthService将以太坊客户端添加到栈中。
// 第二个返回值是完整的node实例,如果节点作为轻量客户端运行,这个值可能是nil。
func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend, *eth.Ethereum) {//轻节点同步if cfg.SyncMode == downloader.LightSync {backend, err := les.New(stack, cfg)if err != nil {Fatalf("Failed to register the Ethereum service: %v", err)}stack.RegisterAPIs(tracers.APIs(backend.ApiBackend))if err := lescatalyst.Register(stack, backend); err != nil {Fatalf("Failed to register the Engine API service: %v", err)}return backend.ApiBackend, nil}backend, err := eth.New(stack, cfg)if err != nil {Fatalf("Failed to register the Ethereum service: %v", err)}if cfg.LightServ > 0 {_, err := les.NewLesServer(stack, backend, cfg)if err != nil {Fatalf("Failed to create the LES server: %v", err)}}if err := ethcatalyst.Register(stack, backend); err != nil {Fatalf("Failed to register the Engine API service: %v", err)}stack.RegisterAPIs(tracers.APIs(backend.APIBackend))// 在同步目标配置完成的情况下,注册辅助的全同步测试服务。if cfg.SyncTarget != nil && cfg.SyncMode == downloader.FullSync {ethcatalyst.RegisterFullSyncTester(stack, backend, cfg.SyncTarget)log.Info("Registered full-sync tester", "number", cfg.SyncTarget.NumberU64(), "hash", cfg.SyncTarget.Hash())}return backend.APIBackend, backend
}

Ethereum 实例根据上下文的配置信息在调用 utils.RegisterEthService() 函数生成。在utils.RegisterEthService()函数中,首先会根据当前的config来判断需要生成的Ethereum backend 的类型,是 light node backend 还是 full node backend。我们可以在 eth/backend/new() 函数和 les/client.go/new() 中找到这两种 Ethereum backend 的实例是如何初始化的。Ethereum backend 的实例定义了一些更底层的配置,比如chainid,链使用的共识算法的类型等。这两种后端服务的一个典型的区别是 light node backend 不能启动 Mining 服务。在 utils.RegisterEthService() 函数的最后,调用了 Nodes.RegisterAPIs() 函数,将刚刚生成的 backend 实例注册到 stack 实例中。

总结的说,api_backend 主要是用于对外提供查询,或者与后端功能性生命周期无关的函数,Ethereum 这类的节点层的后端,主要用于管理/控制节点后端的生命周期。

最后一个关键函数,startNode() 的作用是正式的启动一个以太坊执行层的节点。它通过调用 utils.StartNode() 函数来触发 Node.Start() 函数来启动Stack实例(Node)。在 Node.Start() 函数中,会遍历 Node.lifecycles 中注册的后端实例,并启动它们。此外,在 startNode() 函数中,还是调用了unlockAccounts() 函数,并将解锁的钱包注册到 stack 中,以及通过 stack.Attach() 函数创建了与 local Geth 交互的 RPClient 模块。

// 笔者附加源码
// startNode启动系统节点和所有注册的协议,之后它解锁任何请求的帐户,并启动RPC/IPC接口和矿工。
func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend, isConsole bool) {debug.Memsize.Add("node", stack)// 启动节点本身utils.StartNode(ctx, stack, isConsole)// 解锁任何特定要求的账户unlockAccounts(ctx, stack)// 注册钱包事件处理程序来打开和自动导出钱包events := make(chan accounts.WalletEvent, 16)stack.AccountManager().Subscribe(events)// 创建一个客户端与本地geth节点交互。rpcClient, err := stack.Attach()if err != nil {utils.Fatalf("Failed to attach to self: %v", err)}ethClient := ethclient.NewClient(rpcClient)go func() {// 打开任何已经连接的钱包for _, wallet := range stack.AccountManager().Wallets() {if err := wallet.Open(""); err != nil {log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err)}}// 监听钱包事件直到终止for event := range events {switch event.Kind {case accounts.WalletArrived:if err := event.Wallet.Open(""); err != nil {log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err)}case accounts.WalletOpened:status, _ := event.Wallet.Status()log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status)var derivationPaths []accounts.DerivationPathif event.Wallet.URL().Scheme == "ledger" {derivationPaths = append(derivationPaths, accounts.LegacyLedgerBaseDerivationPath)}derivationPaths = append(derivationPaths, accounts.DefaultBaseDerivationPath)event.Wallet.SelfDerive(derivationPaths, ethClient)case accounts.WalletDropped:log.Info("Old wallet dropped", "url", event.Wallet.URL())event.Wallet.Close()}}}()// 生成一个独立的goroutine用于状态同步监控,如果用户需要,同步完成后关闭节点。if ctx.Bool(utils.ExitWhenSyncedFlag.Name) {go func() {sub := stack.EventMux().Subscribe(downloader.DoneEvent{})defer sub.Unsubscribe()for {event := <-sub.Chan()if event == nil {continue}done, ok := event.Data.(downloader.DoneEvent)if !ok {continue}if timestamp := time.Unix(int64(done.Latest.Time), 0); time.Since(timestamp) < 10*time.Minute {log.Info("Synchronisation completed", "latestnum", done.Latest.Number, "latesthash", done.Latest.Hash(),"age", common.PrettyAge(timestamp))stack.Close()}}}()}// 启动辅助服务(如果启用)if ctx.Bool(utils.MiningEnabledFlag.Name) || ctx.Bool(utils.DeveloperFlag.Name) {// 只有在完整的以太坊节点运行时,挖矿才有意义if ctx.String(utils.SyncModeFlag.Name) == "light" {utils.Fatalf("Light clients do not support mining")}ethBackend, ok := backend.(*eth.EthAPIBackend)if !ok {utils.Fatalf("Ethereum service not running")}// 通过CLI将汽油价格设置为限制,然后开始挖矿gasprice := flags.GlobalBig(ctx, utils.MinerGasPriceFlag.Name)ethBackend.TxPool().SetGasPrice(gasprice)// 开始挖矿threads := ctx.Int(utils.MinerThreadsFlag.Name)if err := ethBackend.StartMining(threads); err != nil {utils.Fatalf("Failed to start mining: %v", err)}}
}

geth() 函数的最后,函数通过执行 stack.Wait(),使得主线程进入了阻塞状态,其他的功能模块的服务被分散到其他的子协程中进行维护。

Node

node节点所在目录,以及相关函数的快速定位

正如我们前面提到的,Node 类型在 geth 的生命周期性中属于顶级实例,它负责作为与外部通信的高级抽象模块的管理员,比如管理 rpc server,http server,Web Socket,以及P2P Server外部接口。同时,Node中维护了节点运行所需要的后端的实例和服务 (lifecycles []Lifecycle),例如我们上面提到的负责具体 Service 的Ethereum 类型。

// Node节点是一个可以注册服务的容器。
type Node struct {eventmux      *event.TypeMuxconfig        *Configaccman        *accounts.Managerlog           log.LoggerkeyDir        string            // 密钥存储目录keyDirTemp    bool              // 如果为true,将通过Stop函数删除密钥目录,因为只是临时目录dirLock       fileutil.Releaser // 阻止并发使用实例目录stop          chan struct{}     // 等待终止通知的通道server        *p2p.Server       // 目前运行的P2P网络层startStopLock sync.Mutex        // Start/Stop函数由一个额外的互斥锁保护state         int               // 跟踪节点的生命周期状态lock          sync.Mutexlifecycles    []Lifecycle // 所有具有生命周期的注册后端、服务和辅助服务rpcAPIs       []rpc.API   // 节点当前提供的api列表http          *httpServer //ws            *httpServer //httpAuth      *httpServer //wsAuth        *httpServer //ipc           *ipcServer  // 存储ipc http服务器信息inprocHandler *rpc.Server // 进程内RPC请求处理程序来处理API请求databases map[*closeTrackingDB]struct{} // 所有开放数据库
}

Node的关闭

在前面我们提到,整个程序的主线程因为调用了 stack.Wait() 而进入了阻塞状态。我们可以看到 Node 结构中声明了一个叫做 stop 的 channel。由于这个 Channel 一直没有被赋值,所以整个 geth 的主进程才进入了阻塞状态,持续并发的执行其他的业务协程。

// Wait函数阻塞,直到节点关闭。
func (n *Node) Wait() {<-n.stop
}

n.stop 这个 Channel 被赋予值的时候,geth 主函数就会停止当前的阻塞状态,并开始执行相应的一系列的资源释放的操作。这个地方的写法还是非常有意思的,值得我们参考。

值得注意的是,在目前的 go-ethereum 的 codebase 中,并没有直接通过给 stop 这个 channel 赋值方式来结束主进程的阻塞状态,而是使用一种更简洁粗暴的方式: 调用 close() 函数直接关闭 Channel。我们可以在 node.doClose() 找到相关的实现。close() 是go语言的原生函数,用于关闭 Channel 时使用。

// doClose释放New()获取的资源,并且收集错误。
func (n *Node) doClose(errs []error) error {// 关闭数据库。这个操作需要锁,因为它需要与OpenDatabase*同步。n.lock.Lock()n.state = closedStateerrs = append(errs, n.closeDatabases()...)n.lock.Unlock()//关闭账户管理器if err := n.accman.Close(); err != nil {errs = append(errs, err)}//如果是临时目录,则全部删除if n.keyDirTemp {if err := os.RemoveAll(n.keyDir); err != nil {errs = append(errs, err)}}// 释放实例目录锁。n.closeDataDir()// 解锁n.Wait.这样就可以解除Wait造成的阻塞close(n.stop)// 报告可能发生的任何错误。switch len(errs) {case 0:return nilcase 1:return errs[0]default:return fmt.Errorf("%v", errs)}
}

Ethereum Backend

我们可以在 eth/backend.go 中找到 Ethereum 这个结构体的定义。这个结构体包含的成员变量以及接收的方法实现了一个 Ethereum full node 所需要的全部功能和数据结构。我们可以在下面的代码定义中看到,Ethereum结构体中包含 TxPoolBlockchainconsensus.Engineminer等最核心的几个数据结构作为成员变量,我们会在后面的章节中详细的讲述这些核心数据结构的主要功能,以及它们的实现的方法。

// Ethereum 实现了以太坊全节点服务.
type Ethereum struct {config *ethconfig.Config// 处理器txPool             *txpool.TxPoolblockchain         *core.BlockChainhandler            *handler // handler 是P2P 网络数据同步的核心实例,我们会在后续的网络同步模块仔细的讲解它的功能ethDialCandidates  enode.IteratorsnapDialCandidates enode.Iteratormerger             *consensus.Merger// 数据库接口chainDb ethdb.Database // 区块链数据库eventMux       *event.TypeMuxengine         consensus.EngineaccountManager *accounts.ManagerbloomRequests     chan chan *bloombits.Retrieval // 接收bloom data数据检索请求的通道bloomIndexer      *core.ChainIndexer             // Bloom索引器在块导入期间运行closeBloomHandler chan struct{}APIBackend *EthAPIBackendminer     *miner.MinergasPrice  *big.Intetherbase common.AddressnetworkID     uint64netRPCService *ethapi.NetAPIp2pServer *p2p.Serverlock sync.RWMutex // 读写互斥锁,保护可变字段(例如汽油价格和etherbase)shutdownTracker *shutdowncheck.ShutdownTracker // 跟踪节点是否以及何时非正常关闭
}

节点启动和停止 Mining 的就是通过调用 Ethereum.StartMining()Ethereum.StopMining() 实现的。设置 Mining 的收益账户是通过调用 Ethereum.SetEtherbase() 实现的。

// 笔者更新源码和注解
// StartMining使用给定的CPU线程数启动矿工。如果挖掘已经在运行,该方法会调整允许使用的线程数,并更新交易池所需的最低价格。
func (s *Ethereum) StartMining(threads int) error {// 更新共识引擎中的线程数type threaded interface {SetThreads(threads int)}if th, ok := s.engine.(threaded); ok {log.Info("Updated mining threads", "threads", threads)if threads == 0 {threads = -1 // 从内部禁用矿工}th.SetThreads(threads)}// 如果矿工没有运行,初始化它if !s.IsMining() {// 将初始价格点传播到交易池s.lock.RLock()price := s.gasPrices.lock.RUnlock()s.txPool.SetGasPrice(price)// 配置本地挖掘地址eb, err := s.Etherbase()if err != nil {log.Error("Cannot start mining without etherbase", "err", err)return fmt.Errorf("etherbase missing: %v", err)}var cli *clique.Cliqueif c, ok := s.engine.(*clique.Clique); ok {cli = c} else if cl, ok := s.engine.(*beacon.Beacon); ok {if c, ok := cl.InnerEngine().(*clique.Clique); ok {cli = c}}if cli != nil {wallet, err := s.accountManager.Find(accounts.Account{Address: eb})if wallet == nil || err != nil {log.Error("Etherbase account unavailable locally", "err", err)return fmt.Errorf("signer missing: %v", err)}cli.Authorize(eb, wallet.SignData)}// 如果开始挖掘,我们可以禁用为加快同步时间而引入的事务拒绝机制。atomic.StoreUint32(&s.handler.acceptTxs, 1)go s.miner.Start(eb)}return nil
}

这里我们额外关注一下 handler 这个成员变量。handler 的定义在 eth/handler.go 中。

我们从从宏观角度来看,一个节点的主工作流需要:
1.从网络中获取/同步 Transaction 和 Block 的数据
2. 将网络中获取到 Block 添加到 Blockchain 中。
handler 就负责提供其中同步区块和交易数据的功能,例如,downloader.Downloader 负责从网络中同步 Block ,fetcher.TxFetcher 负责从网络中同步交易。关于这些方法的具体实现,我们会在后续章节:数据同步中详细介绍。

type handler struct {networkID  uint64forkFilter forkid.Filter // Fork ID过滤器,在节点的生命周期中保持不变snapSync  uint32 // 标志是否启用snap sync(如果我们已经有数据块,则禁用)acceptTxs uint32 // 标志我们是否被认为是同步的(启用事务处理)checkpointNumber uint64      // 同步进度验证器要交叉引用的块号checkpointHash   common.Hash // 同步进度验证器用于交叉引用的块哈希值database ethdb.Databasetxpool   txPoolchain    *core.BlockChainmaxPeers intdownloader   *downloader.DownloaderblockFetcher *fetcher.BlockFetchertxFetcher    *fetcher.TxFetcherpeers        *peerSetmerger       *consensus.MergereventMux      *event.TypeMuxtxsCh         chan core.NewTxsEventtxsSub        event.SubscriptionminedBlockSub *event.TypeMuxSubscriptionrequiredBlocks map[uint64]common.Hash// 用于获取器,同步器,txsyncLoop的通道quitSync chan struct{}chainSync *chainSyncerwg        sync.WaitGrouppeerWG    sync.WaitGroup
}

到此,我们就介绍了 geth 及其所需要的基本模块是如何启动的和关闭的。我们在接下来将视角转入到各个模块中,从更细粒度的角度深入探索 Ethereum 的具体实现。

附录

这里补充一个Go语言的语法知识: 类型断言。在Ethereum.StartMining()函数中,出现了if c, ok := s.engine.(*clique.Clique); ok的写法。该写法是Golang中的语法糖,称为类型断言。具体的语法是value, ok := element.(T),它的含义是如果elementT类型的话,那么ok等于True, value等于element的值。在if c, ok := s.engine.(*clique.Clique); ok语句中,就是在判断s.engine的是否为*clique.Clique类型。

		var cli *clique.Cliqueif c, ok := s.engine.(*clique.Clique); ok {cli = c} else if cl, ok := s.engine.(*beacon.Beacon); ok {if c, ok := cl.InnerEngine().(*clique.Clique); ok {cli = c}}

相关文章:

Go Ethereum源码学习笔记 001 Geth Start

Go Ethereum源码学习笔记 前言[Chapter_001] 万物的起点: Geth Start什么是 geth&#xff1f;go-ethereum Codebase 结构 Geth Start前奏: Geth Consolegeth 节点是如何启动的NodeNode的关闭 Ethereum Backend附录 前言 首先读者需要具备Go语言基础&#xff0c;至少要通关菜鸟…...

idea如何加快创建Maven项目的速度

一、下载archetype-catalog.xml 下载archetype-catalog.xml的地址 二、配置 以下所说的配置都指全局配置。 配置Maven -DarchetypeCataloglocal -Dfile.encodinggbk...

软件外包开发的GO开发框架

近些年GO语言使用的越来越多&#xff0c;尤其是在web应用开发和高性能服务器的项目里。在开发新项目时掌握一些常用的开发框架可以节省开发时间提高工作效率&#xff0c;也是对软件开发人员基本的技能要求。今天和大家分享一些常见的GO语言开发框架&#xff0c;希望对大家有所帮…...

oracle会话打满

1.查看当前连接情况 col machine for a20 col program for a40 col sql_id for a20 set linesize 300 set pagesize 300 select machine,program,sql_id,count(1) from v$session group by machine,program,sql_id order by 4 desc;MACHINE PROGRAM SQL_ID …...

VSCode自定义闪烁光标

打开VSCode 组合键ctrlshiftp搜索"settings.json",打开User Settings 加上这一句 "editor.cursorStyle": "block","workbench.colorCustomizations": {"editorCursor.foreground": "#5c8fb1","terminalCurs…...

复亚智能打造全新云平台:让无人机任务管理更智能、更简单

复亚智能全新升级的MindView云平台&#xff0c;对航线规划、任务管理、自动飞行、数据管理等各个环节开展可视化、数字化、智能化监管&#xff0c;从任务到结果的“看得清”、“管得住”、“查得准”&#xff0c;带来更轻松的操作&#xff0c;改善作业效率、安全保障和用户体验…...

编程导航第六关——白银挑战

编程导航第六关——白银挑战 树的层次遍历 LeetCode102 题目要求&#xff1a;给你一个二叉树&#xff0c;请你返回其按层序遍历得到的节点值。(即逐层地&#xff0c;从左到右访问所有节点)。思路&#xff1a; 我们根据队列的特点&#xff0c;先进先出&#xff1b;要实现全部节…...

743. 网络延迟时间

有 n 个网络节点&#xff0c;标记为 1 到 n。 给你一个列表 times&#xff0c;表示信号经过 有向 边的传递时间。 times[i] (ui, vi, wi)&#xff0c;其中 ui 是源节点&#xff0c;vi 是目标节点&#xff0c; wi 是一个信号从源节点传递到目标节点的时间。 现在&#xff0c;…...

Kubernetes高可用集群二进制部署(四)部署kubectl和kube-controller-manager、kube-scheduler

Kubernetes概述 使用kubeadm快速部署一个k8s集群 Kubernetes高可用集群二进制部署&#xff08;一&#xff09;主机准备和负载均衡器安装 Kubernetes高可用集群二进制部署&#xff08;二&#xff09;ETCD集群部署 Kubernetes高可用集群二进制部署&#xff08;三&#xff09;部署…...

在CSDN学Golang场景化解决方案(微服务架构设计)

一&#xff0c;聚合器微服务设计模式 在Golang微服务架构设计中&#xff0c;聚合器&#xff08;Aggregator&#xff09;微服务设计模式是一种常见的应用程序体系结构模式。该模式旨在简化客户端与后端微服务之间的通信&#xff0c;并支持更高级别的操作&#xff0c;例如聚合多…...

centos7 yum安装mysql5.7

卸载mysql 以下指令查看是否安装过 rpm -qa | grep -i mysql 如果发现已经安装&#xff0c;需要卸载了再安装&#xff08;据说&#xff0c;这样的卸载是不彻底的。&#xff09; rpm -e mysql 卸载 mariadb yum -y remove mariadb-libs-1:5.5.68-1.el7.x86_64 下载和安装mys…...

安防视频汇聚平台EasyCVR视频广场面包屑侧边栏支持拖拽操作

智能视频监控平台EasyCVR能在复杂的网络环境中&#xff0c;将海量设备实现集中统一接入与汇聚管理&#xff0c;实现视频的处理与分发、录像与存储、按需调阅、平台级联等。 TSINGSEE青犀视频汇聚平台EasyCVR可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协…...

爬虫007_python中的输出以及格式化输出_以及输入---python工作笔记025

首先看输出 输出这里,注意不能直接上面这样,18需要转换成字符串 可以看到python中这个字符串和数字一起的时候,数字要转换一下成字符串. 然后这里要注意%s 和%d,这个s指的是字符串,d指的是数字 注意后面的内容前面要放个% ,然后多个参数的话,那么这里用(),里面用,号隔开 然…...

485modbus转profinet网关连三菱变频器modbus通讯触摸屏监控

本案例介绍了如何通过485modbus转profinet网关连接威纶通与三菱变频器进行modbus通讯。485modbus转profinet网关提供了可靠的连接方式&#xff0c;使用户能够轻松地将不同类型的设备连接到同一网络中。通过使用这种网关&#xff0c;用户可以有效地管理和监控设备&#xff0c;从…...

话费充值接口文档

话费充值接口文档 接口版本&#xff1a;1.0 ―、引言 文档概述 本文档提供话费充值接口规范说明&#xff0c;提供一整套的完整的接入示例(http 接口)供商户参 考&#xff0c;可以帮助商户开发人员快速完成接口开发与联调&#xff0c;实现与话费充值系统的交易互联。 公司官网…...

windows系统的IP、路由、网关、内外网同时访问路由以及修改系统文件hosts的配置

当我们刚刚入职一家公司的时候、一般公司会给我下发一个ip地址和mac地址、还有访问一些公司的平台需要修改hosts之后的路由配置、以及第一次配置内网、如何内外网同时上网。 目录 一、ip的配置 1.1、IP的配置 1.2、mac地址的配置 1.3、内外网路由的配置&#xff08;w11系统需…...

Kubespray-offline v2.21.0-1 下载 Kubespray v2.22.1 离线部署 kubernetes v1.25.6

文章目录 1. 目标2. 预备条件3. vcenter 创建虚拟机4. 系统初始化4.1 配置网卡4.2 配置主机名4.3 内核参数 5. 打快照6. 安装 git7. 配置科学8. 安装 docker9. 下载介质9.1 下载安装 docker 介质9.2 下载 kubespray-offline-ansible 介质9.3 下载 kubernetes 介质 10. 搬运介质…...

代码随想录训练营Day59单调栈Part01|739. 每日温度|496.下一个更大元素①

单调栈 单调栈应用场景&#xff1a;找当前元素左边/右边比当前元素大/小的第一个元素什么是单调栈&#xff1a;保证栈里面的元素递增/递减&#xff08;需要自己定义&#xff09;栈内保存什么&#xff1a;存入下标如何比较大小&#xff1a;栈和数组做映射递增&#xff1f;递减&…...

RPMsg-Lite上手

文章目录 1、rpmsg-lite介绍2、rpmsg-lite 应用 现在的芯片非常复杂&#xff0c;很多都是包含多个核&#xff0c;特别是片上系统&#xff08;SoC&#xff09;&#xff0c;一颗芯片上不仅包含了很多个核心&#xff0c;并且很多核心都是异构的。 为了最大限度的发挥他们的性能&am…...

基于YOLOv8 的 多边形区域内目标检测,跟踪,计数

文章大纲 使用OpenCV 进行多边形 角点获取yolov5 的样例实现基于 roboflow 开源库的实现roboflow 开源库 介绍基于YOLOv8 track 的 Polygon Zone 计数参考文献与学习路径自己实现使用 开源库使用OpenCV 进行多边形 角点获取 import cv2 def SetPoints(windowname, img):"…...

Debian系统简介

目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版&#xff…...

【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密

在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题&#xff08;可多选&#xff09; 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘&#xff1a;专注于发现数据中…...

Objective-C常用命名规范总结

【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名&#xff08;Class Name)2.协议名&#xff08;Protocol Name)3.方法名&#xff08;Method Name)4.属性名&#xff08;Property Name&#xff09;5.局部变量/实例变量&#xff08;Local / Instance Variables&…...

python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

Python如何给视频添加音频和字幕

在Python中&#xff0c;给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加&#xff0c;包括必要的代码示例和详细解释。 环境准备 在开始之前&#xff0c;需要安装以下Python库&#xff1a;…...

大模型多显卡多服务器并行计算方法与实践指南

一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...

拉力测试cuda pytorch 把 4070显卡拉满

import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试&#xff0c;通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小&#xff0c;增大可提高计算复杂度duration: 测试持续时间&#xff08;秒&…...

网络编程(UDP编程)

思维导图 UDP基础编程&#xff08;单播&#xff09; 1.流程图 服务器&#xff1a;短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版&#xff0c;柱状图PPT模版&#xff0c;线状图PPT模版&#xff0c;折线图PPT模版&#xff0c;饼状图PPT模版&#xff0c;雷达图PPT模版&#xff0c;树状图PPT模版 图表类系列各种样式PPT模版分享&#xff1a;图表系列PPT模板https://pan.quark.cn/s/20d40aa…...