Page MenuHomePhabricator

[electrum] make TxInput properly support non serialized inputs
ClosedPublic

Authored by PiRK on Sep 15 2023, 15:53.

Details

Summary

When I first created the TxInput class, I intended to keep the constructor simple and always provide it with a scriptsig. It turns out this is not the optimal move, as it would require creating an intermediate dummy incomplete scriptsig when building an input from components (type, keys, optional signatures...) just to take it appart again with TxInput.parse_scriptsig.

This diff makes TxInput support both modes:

  • inputs from a serialized source
  • inputs from keys, type, singatures...

Two factory methods from_scriptsig & from_keys are added to keep a specialized API for each use case, and a helper factory from_coin os added to select the proper factory based on a coin dict.

This allows moving some of the TxInput building logic out of the Transaction class for size estimation and TxInput building from coin dicts. More code will be moved in the next diff (Transaction.input_script(txin), Transaction.get_siglist(txin) & Transaction.get_sorted_pubkeys(txin)

The mock scriptsig in TestSweep need to be fixed to be proper push operations to avoid an error when attempting to parse them.
The consolidation test needs better mock coins ("num_sig", "signatures", "x_pubkeys" & "pubkeys" are expected to be always present)

Depends on D14491

Test Plan

python test_runner.py

Diff Detail

Repository
rABC Bitcoin ABC
Lint
Lint Not Applicable
Unit
Tests Not Applicable

Event Timeline

PiRK requested review of this revision.Sep 15 2023, 15:53
bytesofman added a subscriber: bytesofman.
bytesofman added inline comments.
electrum/electrumabc/tests/test_consolidate.py
35–38 ↗(On Diff #42229)

why weren't these here before?

electrum/electrumabc/tests/test_transaction.py
690 ↗(On Diff #42229)

why is this coming before the bytes.fromhex( call now?

electrum/electrumabc/tests/test_wallet.py
190 ↗(On Diff #42229)

The mock scriptsig in TestSweep need to be fixed to be proper push operations to avoid an error when attempting to parse them.

That's what's happening here?

Is this error throwing for not having a push op introduced in this diff / could it have an impact beyond the scope of this diff?

electrum/electrumabc/transaction.py
186 ↗(On Diff #42229)

are these already typed? i.e. num_required_sigs seems typed as Optional[int] in the inputs. Does python require also typing it here?

self.scriptsig on line 178 is not typed, though it is typed in the input params.

187–191 ↗(On Diff #42229)

same question

425 ↗(On Diff #42229)

what's the difference between this and del d["scriptSig"]?

This revision now requires changes to proceed.Sep 15 2023, 20:00
electrum/electrumabc/tests/test_consolidate.py
35–38 ↗(On Diff #42229)

Previously the TxInput was constructed with a dummy scriptsig (in Transaction.txinputs()). Now it is constructed via TxInput.from_keys, which makes these values mandatory.

electrum/electrumabc/tests/test_transaction.py
690 ↗(On Diff #42229)

scriptsig is now an optional argument and the sequence number is a mandatory positional argument in the TxInput.__init\\ constructor, so I had to change the order of the parameters to have the mandatory argument before the optional arguments.

I thought it would be best to have the arguments in the same order for TxInput.from_scriptsig than in the constructor, even though in from_scriptsig the scriptsig is also mandatory. It is just for consistency.

electrum/electrumabc/tests/test_wallet.py
190 ↗(On Diff #42229)

Previously the TxInput class was accepting garbage as a scriptsig, now it requires a well formed script, which was already required by the legacy code when deserializing an input.
This test was added very recently (last week) and never used the legacy deserialization code, that's why I got away with using garbage data as a scriptsig.

electrum/electrumabc/transaction.py
186 ↗(On Diff #42229)

I'm not actually sure about this. Some type analysis tools seem to assume that the attribute is of the same type as what is passed to him from the input argument, but that is not necessarily always correct. For example here the type are not exactly the same:

class Tx:
    def __init__(self, inputs: Optional[List] =: None, outputs: Optional[List] =None) -> List:
         self.inputs: List = inputs if inputs is not None else []
         self.outputs: List = outputs if outputs is not None else []

I think it is best to specify the type on both to avoid any ambiguity.
I will add the typehint to scriptsig as well.

425 ↗(On Diff #42229)

del would always raise an error if scriptsig is not specified. It only deletes the item from the dict.
pop also returns the item before deleting it from the dict (unused here), and allows specifying a default value to return in case the item is not found. If a default value is specified, it will not raise an error if the item is not in the dict.

So this changes fixes an error if d does not have scriptsig. Previously this was never possible, as the scriptsig was mandatory in __init__. Now it is possible to reach this line without having a scriptsig in the dict, if the input was created with from_keys.

consistently always specify typehints, and remove debugging traceback and assertion

electrum/electrumabc/transaction.py
186 ↗(On Diff #42229)

Also previously scriptsig never changed after __init__, whereas the other attributes (_pubkeys...) could be initially None, then assigned later when parsing the scriptsig.
I anticipate that scriptsig will be computed on demand and cached for complete (fully signed) inputs in a subsequent diff, when I move the TxInput serialization code to the class.

This revision is now accepted and ready to land.Sep 16 2023, 14:26