Bitcoin-ABC/ElectrumABC

Label sync issues

PiRK opened this issue · 1 comments

PiRK commented

#166 is an attempt to re-enable the LabelSync plugin that has been disabled ever since the fork from Electron Cash.
This is a very requested feature, but it's current design has some major flaws.

To summarize the way the plugin works, there is a server running that stores the encrypted labels in a database. A wallet is identified by a hash of it's master public key. Encrypted labels associated with a given wallet are indexed by their encrypted txid. The servers also stores a nonce for each label. This helps the client figure out if his version of a label is outdated. The server and the client store the highest nonce as a "wallet nonce", which is used to determine whether the wallet needs to be synced and which of the client or server is most up to date.

There are 3 basic things the servers does:

  • register a new label or update an existing label
    Client plugin:

          bundle = {"walletId": wallet_id,
                    "walletNonce": nonce,
                    "externalId": self.encode(wallet, item),
                    "encryptedLabel": self.encode(wallet, label if label else '')}
          self.do_request("POST", "/label", False, bundle, True)
          # Caller will write the wallet
          self.set_nonce(wallet, nonce + 1)
    

    Server: https://github.com/maran/electrum-sync-server/blob/master/sync_master.go#L56

  • register multiple new labels (initial upload)
    Client:

          bundle = {"labels": [],
                    "walletId": wallet_id,
                    "walletNonce": self.get_nonce(wallet)}
          for key, value in wallet.labels.items():
              try:
                  encoded_key = self.encode(wallet, key)
                  encoded_value = self.encode(wallet, value)
              except:
                  self.print_error('cannot encode', repr(key), repr(value))
                  continue
              bundle["labels"].append({'encryptedLabel': encoded_value,
                                       'externalId': encoded_key})
    
          self.do_request("POST", "/labels", True, bundle)
    

    Server: https://github.com/maran/electrum-sync-server/blob/master/sync_master.go#L75

  • serve all labels for a wallet since a nonce (used for sync or initial download):

    Client:

    response = self.do_request("GET", ("/labels/since/%d/for/%s" % (nonce, wallet_id)))
    

    Server: https://github.com/maran/electrum-sync-server/blob/master/sync_master.go#L116

This design has the benefit of offering privacy. The xpub is hashed. The txid and label are encrypted.
The drawback is that the server cannot determine whether the data is a genuine txid and label. It could be random spam filling up progressively the disk space. As far as I can tell, there is not DoS mitigation at all in the server code. It seems to only have some timeout preventing downloading or uploading too much data by a client. I can't find this timeout anywhere in the code, so I'm assuming it is the default for whatever framework the server uses.

BCH and eCash users run into timeout, when attempting to synchronize large wallets with a lot of labels.
One of the issue that has been identified is that uploading multiple labels causes multiple database accesses: maran/electrum-sync-server#3

Improvement ideas:

  • optimize the server to batch database updates for force upload0
  • implement spam mitigation on the server side to avoid disk overflow attacks
  • don't hardcode a default sync server, let the user specify a server url
  • figure out an economic incentive model for running a sync server, and/or update the plugin and server to support user registration; a user would need to register their wallet id (hash of the xpub) and specify a password for it. Requests for unregistered wallets could simply be ignored by the server.
PiRK commented

There are users interested in the feature, but no one seems to be able to come up with a scalable solution, and cloud storage solutions are not easy for users to enable (need to jump through hoops for an API key)