当前位置: 首页 > 工具软件 > Electrum > 使用案例 >

Electrum 钱包源码解读(二)

缑嘉玉
2023-12-01

前言

在ubuntu上安装了PyCharm,并使用其打开下载的Electrum代码,安装及使用方式如下:
(https://blog.csdn.net/zhuanshu666/article/details/73554885)

代码地址如下:

(https://github.com/spesmilo/electrum)

下面开始代码的运行。

在路径electrum-master下找到 run_electrum 文件,将文件中的所有方法逐一打上断点之后,使用Run->Toggle Line Breakpoint 后 debug 来逐行运行,可以看到代码的运行顺序:

  1. if is_local or is_android
    a. 用于判断是在本地运行或者是在安卓端运行,如果是其中的一项,则将 script_dir + 'packages’这个路径加入 sys.path中
    b. 由于是在本地运行所以此时 is_local=true,is _android=false

在导入这几个依赖是有几个需要注意的地方

  1. if not is_android:该方法的运行是基于第一步的判断
    a. 随后开始跳转到 check_imports() 方法

  2. def check_imports():

    1. 程序会在这个方法中检查try_catch 中的几个依赖是否导入,如果没有导入,程序会报错
    2. 在导入这几个依赖是有几个需要注意的地方
      1. 可以使用本机的终端会cmd工具来导入,也可以使用PyCharm自带的 Terminal终端工具进行命令行的执行
      2. 如果发现某个依赖找不到,可以使用pip search <model_name> 进行查询
      3. 在导入 dns 这个依赖时,应该使用 pip install dnsPython 而不是 pip install dns
      4. 导入 google.protobuf ,使用 pip install protobuf
    3. 之后程序会顺序执行 Line67- Line324, 直至进入 Line-327
  3. if __name__ == '__main__':

    1. 该方法为程序的主入口方法,进入该方法后程序会检查当前环境的各种配置如:wallet_path、testnet、cmd 等
    2. 此时程序会进入 if cmdname== 'gui':
    3. 而后执行
      • d.init_gui(config, plugins)
      • 进而跳转到
      • daemon.py --> def init_gui(self, config, plugins):在此方法中进行 gui 的初始化操作
  4. daemon.py 文件中的 def init_gui(self, config, plugins):

    1. 在此方法中会首先进行 ‘gui’ 对象的实例化

      • self.gui = gui.ElectrumGui(config, self, plugins)
      • 实例化方法在 _init_.py 中的
      • def __init__(self, config, daemon, plugins):
      • 而后程序会返回daemon.py 文件中的 def init_gui(self, config, plugins):
      • 去执行 try: self.gui.main()
      • 即跳转到_init_.py 中的 def main(self):方法中,代码如下:
      def main(self):
          try:
              self.init_network()
          except UserCancelled:
              return
          except GoBack:
              return
          except BaseException as e:
              traceback.print_exc(file=sys.stdout)
              return
          self.timer.start()
          self.config.open_last_wallet()
          path = self.config.get_wallet_path()
          if not self.start_new_window(path, self.config.get('url'), app_is_starting=True):
              return
          signal.signal(signal.SIGINT, lambda *args: self.app.quit())
      
          def quit_after_last_window():
              # on some platforms, not only does exec_ not return but not even
              # aboutToQuit is emitted (but following this, it should be emitted)
              if self.app.quitOnLastWindowClosed():
                  self.app.quit()
          self.app.lastWindowClosed.connect(quit_after_last_window)
      
          def clean_up():
              # Shut down the timer cleanly
              self.timer.stop()
              # clipboard persistence. see http://www.mail-archive.com/pyqt@riverbankcomputing.com/msg17328.html
              event = QtCore.QEvent(QtCore.QEvent.Clipboard)
              self.app.sendEvent(self.app.clipboard(), event)
              self.tray.hide()
          self.app.aboutToQuit.connect(clean_up)
      
          # main loop
          self.app.exec_()
          # on some platforms the exec_ call may not return, so use clea
      
    2. 在此方法中,程序会根据本地的路径来判断是否已存在钱包,如存在则打开,不存在则创建新钱包

      1. 首先创建钱包的UI —
      path = self.config.get_wallet_path()
      if not self.start_new_window(path, self.config.get('url'), app_is_starting=True):
      
      1. 随后程序会跳转到_init_.py 中的
      def start_new_window(self, path, uri, app_is_starting=False):
      
      1. start_new_window方法中,程序会再次在本地路径中判断钱包是否存在,如果不存在,则执行
      storage = WalletStorage(path, manual_upgrades=True)
      wizard = InstallWizard(self.config, self.app, self.plugins, storage)
      
      1. 此时程序会跳转到 installwizard.py 文件中执行实例化方法def __init__(self, config, app, plugins, storage):

        1. 在这份代码中首先运行BaseWizard.__init__(self, config, plugins, storage)
          • 改方法存在于base_wizard.py
        2. 在此方法中我们可以修改部分UI,例如钱包的名称:
        self.setWindowTitle('HxWallet  -  ' + _('Install Wizard'))
        
      2. def __init__(self, config, app, plugins, storage):方法中,程序会顺序执行到self.show() 此时 gui画面会开始展示。

      3. 此时程序会跳转到 _init_.py 文件中 执行

        try:
            wallet = wizard.run_and_get_wallet(self.daemon.get_wallet)
        

        在这里来执行一些针对gui上点击事件的操作,例如:用户点击了 ‘取消’ 、‘返回’ 等按钮。
        继而,跳转到 installwizard.py 文件中 的 def run_and_get_wallet(self, get_wallet_from_daemon):方法中。

      4. def run_and_get_wallet(self, get_wallet_from_daemon): 方法中会针对用户的选择例如用户的钱包是否存在进行一些判断。

      5. 在UI上点击了 Next 按钮之后,程序会跳转到

        • installwizard.py文件中的 if self.storage.requires_split():
        • 而后继续顺序执行至:self.run(action)
      6. 此后,程序跳转至base_wizard.py 中的 def new(self):方法,并在此方法中进行UI的初始化工作,初始化完成后,UI上回展示 让用户来选择希望创建的钱包类型。

      7. 选择了其中一项并点击next按钮之后,程序会执行

        def on_wallet_type(self, choice):
           self.wallet_type = choice
           if choice == 'standard':
               action = 'choose_keystore'
           elif choice == 'multisig':
               action = 'choose_multisig'
           elif choice == '2fa':
               self.load_2fa()
               action = self.storage.get_action()
           elif choice == 'imported':
               action = 'import_addresses_or_keys'
           self.run(action)
        

        在这里选择第一项Standard wallet,而后点击 Next

      8. 随后程序会执行 def choose_keystore(self):方法来让用户选择创建种子或使用已有种子来恢复一个钱包,

        • 在弹出的选择页面上选择Create a new seed ,而后点击 Next,
        • 程序会执行base_wizard.py 文件中的def run(self, *args):
        • 在条件判断
        elif hasattr(self, action):
           f = getattr(self, action)
           f(*args
        

        中执行跳转至:def choose_seed_type(self):

      9. 此时程序跳转到 installwizard.py 文件中的 def wizard_dialog(func): ,随后在执行

        try:
            out = func(*args, **kwargs)
        

        时跳转到 Seed 类型的选择页面 Choose Seed type

        • 在这里选择: Standard 并点击Next 按钮
        • 程序进入base_wizard.py 中的
        • def create_standard_seed(self): self.create_seed('standard') 方法
        • 然后再次进入 installwizard.py 文件中的 def wizard_dialog(func):
        • 此时UI页面会跳转到 seed 的显示及提示页面。
        • 在此UI页面上点击Next 按钮,程序会跳转到installwizard.py 中的
          if type(out) is not tuple:
              out = (out,)
          run_next(*out)
          

        进而运行base_wizard.py 中的

        • def request_passphrase(self, seed, opt_passphrase):
        • def confirm_seed(self, seed, passphrase):
        • 后 再次进入 installwizard.py 中的 def wizard_dialog(func)

        之后UI跳转至Confirm Seed 页面

      10. 在输入框中输入Seed 后点击Next 后程序跳转至base_wizard.py中的def confirm_passphrase(self, seed, passphrase):

      11. 随后程序进入 def create_keystore(self, seed, passphrase): 创建秘钥

        • 中进行一系列的判断,如果条件满足,则进行钱包的创建
        • base_wizard.py —> def create_wallet(self):
        • 在此方法中程序会先判断钱包有无密码,若没有,则执行 self.request_password
        • 来跳转到UI密码设置页面
        • 而后执行def on_password(self, password, *, encrypt_storage, storage_enc_version=STO_EV_USER_PW, encrypt_keystore):
        • 进行密码的设置
      12. 然后程序顺序执行到self.run('create_addresses') 来创建地址

      13. 此方法的定义在 base_wizard.pydef create_addresses(self):

      14. 程序再次回到_init_.py --> w = self.create_window_for_wallet(wallet)

      15. _init_.py --> def create_window_for_wallet(self, wallet):

      16. 程序再次执行 _init_.py --> if not self.start_new_window(path, self.config.get('url'), app_is_starting=True):

        • 而后继续顺序执行值 self.app.exec_()
  5. 至此,一个stantard 类型的钱包创建完毕,也可以看到完整的,包含History Send Receive的UI界面出现

 类似资料: