-
Notifications
You must be signed in to change notification settings - Fork 947
Description
First off: SPV is worse security than fullnodes. Please use fullnodes if you can.
On the other hand, if you trust a particular cloud service provider, you can run a fullnode on a cloud service, and thus not spend a lot of bandwidth in your own local connection. You might then think to run lightningd
locally and point the --bitcoin
parameters at the cloud service fullnode, but this actually downloads every block in full and gets you as much bandwidth use as a locally-run bitcoind
with blocksonly
option. You could instead run an SPV server of some kind on your remote cloud service fullnode, and as long as the cloud service does not secretly replace your bitcoind
with minercoin2xd-unlimitedvision
, you should be fine. Then we would want support for SPV in a locally-run lightningd
to limit the bandwidth needed, and point it only at your trusted cloud service fullnode.
Here is my current plan:
When lightningd/bitcoind
object is initialized, it checks for the following two sets of APIs:
getrawblockbyheight
,getutxout
,estimatefees
,getchaininfo
,sendrawtransaction
- current 0.9.0 set.gettxesbyheight
,getutxobyscid
,estimatefees
,getchaininfo
,sendpsbttomempool
,checkspent
- next version of the backend plugin API.
gettxesbyheight
is given a height
to fetch, plus a receive_scriptpubkeys
an array of hex string scriptPubKey
s to check, and a spend_utxos
an array of objects with txid
, vout
, and scriptPubKey
fields. It returns a field found
as false
if not yet found, or if the block is found it returns found
field as true
and in addition returns the blockid
the hash of the block header, header
the hex string dump of the header, and txes
an array of hex string transactions in the block that match the given receive_scriptpubkeys
or spend_utxos
.
getutxobyscid
is given an scid
of BOLT-standard form BBBxTTxOO
. It returns a field found
, which is false
if the given SCID does not match an unspent SegWit v0 32-byte witness (P2WSH), or a SegWit v1 32-byte witness (Taproot). If true
it returns the scriptPubKey
of the output as well as the amount
.
sendpsbttomempool
is given a Base64 PSBT that contains a completely signed transaction, and a allowhighfees
parameter, and broadcasts it. We have to switch bitcoind_sendrawtx
to use struct bitcoin_tx
which conveniently contains a PSBT.
checkspent
is given an array of objects. Each object represents a txo with at least txid
and vout
and scriptPubKey
fields, and optional blockheight
, which is the known confirmation height of the txo. For each txo, it checks if the txo is spent, and if so, adds it to the spent
result, which is an array of objects with txid
, vout
, and spendheight
fields, with 'spendheight': 0
for TXOs that are spent in unconfirmed transactions.
Internally, we also have these changes to lightningd/bitcoind
functions:
bitcoind_can_getutxobyscid
. This accepts aconst struct bitcoind *
and returnstrue
if the bitcoin backend implementsgetutxobyscid
.- If it returns true, it is safe to call a new
bitcoind_getutxobyscid
andbitcoind_checkspent
, but not safe to callbitcoind_getfilteredblock
orbitcoind_getutxout
. - If it returns
false
, it is not safe to callbitcoind_getutxobyscid
orbitcoind_checkspent
but it is safe to callbitcoind_getfilteredblock
andbitcoind_getutxout
.
- If it returns true, it is safe to call a new
bitcoind_gettxesbyheight
. This is always callable.chaintopology
should use this API instead ofbitcoind_getrawblockbyheight
, since the latter is removed.- The only change in interface relative to
bitcoind_getrawblockheight
is thatbitcoind_gettxesbyheight
requires filter arguments. It still returns astruct block
albeit one with a possibly-incomplete set oftx
. - If the backend plugin is using the 0.9.0 interface this just calls into
getrawblockbyheight
and ignores the filter arguments.
- The only change in interface relative to
- As mentioned,
bitcoind_sendrawtx
should acceptstruct bitcoin_tx
instead ofconst char *hextx
.
Finally, we also add a new lightningd
command, checknewblock
, which triggers chaintopology
into checking for a new block. The backend plugin should call this when the block tip height changes. Normal bitcoin-cli
and BIP158 plugins will poll, though a BIP158 plugin could have a peer push a new block at it (not likely unless the peer is a miner). An Electrum plugin would subscribe to blockchain.headers.subscribe
. For now we also do polling ourselves in lightningd
to support older plugins that do not perform this call.
After we change lightningd
, we can then update bcli
to use the new interface. In order to check the new interface, gettxesbyheight
will also apply filtering to transactions in the block, and will not provide txes that do not match the given filters. getutxobyscid
could have a simple on-disk cache: a mapping of SCID to either a tuple of txid
and vout
, or Nothing. If an SCID exists in the on-disk cache, and it maps to nothing, it is known-invalid SCID, else if it maps to a txid
vout
the bcli
will go query bitcoin-cli getutxout
to see if it is still unspent. If the SCID does not exist in the on-disk cache, then we scan the block and update the cache, and we also iterate over all possible SCIDs (e.g. even absurd ones like BBBx16777215x65535) and map those that do not match a P2WSH or Taproot output to Nothing.
Original text:
First off: SPV is worse security than fullnodes. Please use fullnodes if you can.
Currently, the interface to bcli
inherently assumes the backend is a fullnode of some kind. This fullnode is always fully trusted to not lie, and we always download every block from the backend.
When doing block synchronization, what we can do is to have a txfilter
containing:
- UTXOs which, if spent, we want to know about.
- Addresses which, if spent from or received into, we want to know about.
Now, it is important that we keep track of UTXOs that we want to track spendedness of channels. However, each such UTXO has its own address as well.
Most SPV protocols (eg BIP158, Electrum) are address-centric. So, we should provide just addresses to the backend. The backend should inform us
So I propose for the bcli
getrawblockbyheight
:
- Add a new required parameter,
txfilter
, which is an array ofscriptPubKey
hexdumps. - It may return a result without a
block
field (but must still returnblockhash
). This is used to informlightningd
that the block at the given height does not involve any of the addresses in thetxfilter
. - It has to return a separate
blockheader
field regardless. This is needed so that chaintopology can recognize reorgs.
Then:
- For Electrum, we do
blockchain.scripthash.get_history
on every listedscriptPubKey
, and if any of them has a transaction whose confirmedheight
equals the height being queried, then we should download that block (elsewhere) and provide it to thelightningd
.- But we should be wary of reorgs. Thus, we should do a
blockchain.block.header
and save the header, thenblockchain.scripthash.get_history
for a single scriptpubkey, then ablockchain.block.header
and compare if it is the same as previous; if not the same, we should restart the querying again, since the alternate block might now contain the previousscriptpubkey
. This is not resilient against very fast ABA problems but hopefully those are rare.
- But we should be wary of reorgs. Thus, we should do a
- For BIP158, the plugin maintains a header chain, and we download the version
0x00
Neutrino filter for the block header hash at the requested height. Then we check if anything in ourtxfilter
matches the Neutrino filter.
Ideally, our fullnode-trusting bcli
should also apply the txfilter
to blocks it returns. This is to ensure that any bugs in maintaining the lightningd
-side txfilter
can be caught by our test suite without having to add SPV plugins of our own.
Unfortunately, to determine if an address is spent from, we would require a txindex
, or have the plugin maintain its own transaction index. Both are obviously undesirable.
However, in practice:
- We only care about addresses we receive from.
- We only care about UTXOs that somebody spends from.
So let me propose instead passing in two arguments to getrawblockbyheight
of bcli
:
txfilter_receive_addr
, an array ofscriptpubkey
s, which, if any of them receive funds in the given block, we should return the block.txfilter_spend_utxo
, an array of{'txid': 'xx', outnum: 42, scriptpubkey: 'xx'}
, which, if any of the given UTXOs are spent, we should return the block.
Then:
- The default fullnode-trusting
bcli
only needs to scan the requested block and every transaction:- If a transaction input has a
prevout
matching atxfilter_spend_utxo
entry, return the block. - If a transaction output has a
scriptPubKey
matching atxfilter_receive_addr
entry, return the block. - Otherwise do not return the block, just its header.
- If a transaction input has a
- The SPV interfaces, which are all address-based, just get the
scriptpubkey
from thetxfilter_spend_utxo
and append them to thetxfilter_receive_addr
.
This allows us to check in our test suite that lightningd
is maintaining its txfilter
correctly, using only the bcli
plugin, while allowing address-based SPV interfaces to be used.
Activity
ZmnSCPxj commentedon Jul 24, 2020
For now, a minimal scan of the
chaintopology
component suggests we keep track of the below:struct txfilter
owned instruct lightningd
.struct txowatch_hash
owned instruct chain_topology
.struct txwatch_hash
owned instruct chain_topology
.Unfortunately, BIP158 does not include entire transactions: it only includes spends from and spends to specific
scriptPubKey
.However, transactions-being-tracked is only used in two places:
lightningd/peer_control.c
uses it to check for the funding transaction to confirm. It knows only the txid, but the funding TXO is known to have a specificscriptPubKey
.lightningd/onchain_control.c
uses it to check for transactions being confirmed or deconfirmed (based on reorgs) and restartonchaind
. It knows the entire transaction being watched.So I think we can modify the
watch_txid
function and thestruct txwatch
structure to include an address that the transaction is funding.scriptPubKey
, so we can provide this.onchain_control
we know the entire transaction, we can just extract anyscriptPubKey
from any output.That way, our only interface absolutely necesary is a
scriptPubKey
that is spent to.Of note as well, is that
watch_txo
is only used in the same places as well. We also need to modifywatch_txo
to add thescriptPubKey
the UTXO has, which is known in both places as well.txnums
field fromstruct block
. #3873ZmnSCPxj commentedon Jul 24, 2020
Okay, I think
getrawblockbyheight
is just a bit too low-level.Instead, I propose a new
bcli
method.getfilteredblockbyheight
height
receive_scriptpubkeys
spent_utxos
DESCRIPTION
getfilteredblockbyheight
returns the block header, and any appropriate transactions, of the block at the givenheight
.receive_scriptpubkeys
is an array of BitcoinscriptPubKey
s; each is a hexadecimal string.spent_utxos
is an array of objects.Each object contains the fields
txid
andoutnum
which indicate the UTXO being monitored, as well as a fieldscriptpubkey
which is thescriptPubKey
of that UTXO.RETURN VALUE
This returns a result object with the following fields:
blockheader
, a hexadecimal string representation of the block header.txes
, an array of hexadecimal string representations of transactions in the block.This command returns
null
for the above fields if the backend has not reached that block height yet.The
txes
array reliably contains only those transactions which spend from one of the given UTXOs inspent_utxos
, or transactions which pay to one of the givenscriptPubKey
s inreceive_scriptpubkeys
.However, it may report more transactions than that (i.e. it may add transactions that false-positive match the given constraints).
Clients of this method should not rely on anything other than the transactions that match being returned, and should be prepared to handle transactions that do not match the constraints.
By using the above, we can use just an Electrum Stratum connection. Electrum Stratum does not look like it can support downloading entire blocks, which is what
getrawblockbyheight
should return. But it can provide the history of ascriptPubKey
i.e. all transactions that spend from and pay to that address, and that is why we should switch to the above interface instead.Thoughts? @darosior in particular?
ZmnSCPxj commentedon Jul 24, 2020
Hmmm so I was looking at our code as well, and it seems we have an existing
bitcoind_getfilteredblock
API as well.As best as I can determine, this
bitcoind_getfilteredblocks
reports the TXOs created in the block which are P2WSH outputs and are still unspent now. It is used when querying the information from an SCID.Electrum has a specific
blockchain.transaction.id_from_pos
which is perfectly designed for SCID queries. However, plain Bitcoin does not have a simple way of getting a transaction from a specific block at a specific index in that block. And neither can BIP158 Neutrino --- how the heck does Neutrino validate SCIDs??Hmmm.
Now, in order to prevent too much activity from interacting with
bitcoind
,lightningd
caches blocks and the P2WSH TXOs created in the block. This caching is of course done in the database. So if some SCID claims to have been created in a blocklightningd
has not downloaded, we go download it, checking only the P2WSH unspent outpoints. Then we store the block in the database, plus all P2WSH unspent outpoints, in the db, and then we check if the SCID is actually valid.However, the above will not work well with Electrum, since we cannot simply download a block.
Further, the spentness of P2WSH outpoints depends on us processing every transaction in a block. We cannot just depend on data that involves only our own UTXOs and addresses!
Hmmmm. I suppose this needs more design thinking.
ZmnSCPxj commentedon Jul 24, 2020
All right, let us add a new
getutxobyscid
. It returns the exacttxid
, amount,scriptPubKey
, and the height was spent at (or 0 if it is not spent) and afound
field that istrue
. If the SCID given does not match to any transaction outpoint, it just returns afound
field that isfalse
.A backend plugin may choose not to implement
getutxobyscid
. It signals this by providing agetutxobyscid
that always fails using a specific error code. If so, it must commit to implementinggetrawblockbyheight
.The backend plugin
getfilteredblockbyheight
should also return a flag,all
. If this flag is present and set, then the backend gave all the transactions in the block. If absent, orfalse
, then the backend did not give all transactions in the block.We also need to create a new db table,
utxoset_blocks
, with the same schema asblocks
. When upgrading db, it just copies theblocks
table.The
utxoset_blocks
is those blocks which we know we have seen in full, i.e. not filtered as above.Then, in
gossip_control
, if agossip_get_txout
message is received:utxoset
table.wallet_outpoint_for_scid
to report spendedness -- the current version returns failure if it is listed but marked as spent.gossipd
.utxoset_blocks
table.utxoset_blocks
is filled in for every height from the SCID height to the max height, we know that we are tracking spendedness correctly, and we can return a success togossipd
.getutxoset
frombcli
. If it returns success, we know that it is still unspent, so we can return a success togossipd
. If it fails, record the current tip height as thespendheight
in theutxoset
table (so future queries for the same SCID return quickly).utxoset
table, check if the block is in theutxoset_blocks
table, in which case we know the SCID is invalid and was never created anyway, so we can report failure togossipd
.getutxobyscid
bcli
.found
astrue
, store the data intoutxoset
.gossipd
if it is not spent yet, else report failure.found
asfalse
, report failure togossipd
.getutxobyscid
fails with the "not implemented" error code, fall back to the existingbitcoind_getfilteredblock
, which usesgetrawblockbyheight
.wallet_filteredblock_add
should add blocks to theutxoset_blocks
table.Then, on normal synching at the block tip:
getfilteredblockbyheight
.blocks
, just like now.all
as true, also add it to theutxoset_blocks
table, and check for transactions that spend items in theutxoset
.all
as false, it requeriesgetutxoset
on allutxoset
entries that are unspent, and if they are now spent, records the current block as the spending height.Thoughts? @cdecker in particular since this is a massive change in chaintopology and wallet db. Sigh. The complication is in short-channel-ids.
darosior commentedon Jul 24, 2020
Supporting protocol that expose a less natural (scriptPubkey based instead of txid / txout based) interface would be great as it would allow to use more different backends than just
bitcoind
oresplora
, such as for example the low-storage-costelectrs
.That said, I'm not sure it makes sense for us to support Stratum as we fetch a lot of outputs and such backends would be quite inefficient or don't work at all (EPS for example) as they are designed for a single onchain wallet (so there would just be the Python Electrum supported in addition to the Blockstream fork of electrs).
This comes down to the reason I did not go for this initially: efficiency. We moved from a
getoutput
-based (3 calls by output tobitcoind
by channel ann) polling toget_filteredblock
(1 call tobitcoind
each reasonable amount of time) for more efficiency with gossip.So moving back to a one-call-per-channel-ann would be kind of a regression unless the plugin does some sort of caching, and in which case it needs to cache the state to instantly answer to
lightningd
'sgetoutputbyscid
.This means more complexity, and what would have been small logic changes to
lightningd
(eg support a new Element hotness, by changing the parsing of the raw blocks received) needs to be replicated to all plugins.There is still BIP158 which would be nice to have though. So this is trading (a lot of) complexity for BIP158 support: I have no strong opinion on this.
ZmnSCPxj commentedon Jul 24, 2020
BIP158 does not have any specific support we can get for the
short-channel-id
-lookup case.What we can do with BIP158 would be to continue with the current system, which is that
lightningd
requests for the raw block, and the BIP158 backend connects to a random fullnode and downloads the entire block. We store the transaction outputs that are P2WSH, and determine their spendedness. That means adding an advisoryscriptPubKey
argument to thegetutxout
command, which is ignored inbitcoin-cli
plugin, but informs BIP158 backend to scan all saved filters for thescriptPubKey
being spent.But caching at the plugin level is probably needed indeed, especially for Electrum and BIP158 backends. Sigh.
Then a backend plugin could support a
getutxobyscid
. If this method is found we use it exclusively ingossip_get_txout
requests and ignore thelightningd
db, on the assumption the backend has implemented all the needed optimizations, in particular the Electrum backend has a specific command for such queries.Maybe the block-synching command can be allowed to return multiple options. It gets a set of UTXOs and addresses the
lightningd
wants to monitor. Then it could return ONE of the following:getrawblockbyheight
.Thus, a plugin need not parse block data. However, the array of transactions return would be used by an Electrum backend, and presumably the Electrum server is the one that parsed the block for us, so the new Elements parsing thing need not be understood by the plugin either. BIP158 backends would just match the addresses to the BIP158 filters, and download the entire block as well too.
If the block-synch command returns:
Then
lightningd
knows it can acquire accurate SCID-create and SCID-delete information for that block.But if the block-synch command returns:
Then
lightningd
knows that it cannot get SCID-create and `SCID-delete information for that block.So I think a new table of blocks that we have full knowledge of SCID events in that block should be maintained by us (i.e. the
utxoset_blocks
I proposed above).If
lightningd
gets agossip_get_txout
, if a plugin implementsgetutxobyscid
then we just depend on that. Else:utxoset_blocks
table. If it is, go to the next step.getrawblockbyheight
, which must exist. A Stratum backend can get away with not implementing this (or providing one that always errors) by simply implementinggetutxobyscid
.utxoset
table.utxoset_blocks
table. If so, that is sufficient for us to know that it is still unspent now.isutxospent
. This includes the UTXO itself, but also includes an advisory array of blockheights that are missing in theutxoset_blocks
table. from the known creation height of the UTXO. The BIP158 backend plugin can use that to limit its filtering to only the specified blocks. We can then update the entry inutxoset
if it turns out that the UTXO is spent at one of those heights. There is a small false-positive rate here, in that the UTXO could end up false-matching the filter at that height, in which case we would reject an SCID needlesly, but hopefully that is a small enough effect on the network (the alternative would be for the plugin to download the block and parse it to check for UTXOs being spent in inputs, which is exactly what we want to avoid).The upshot of this is that with a BIP158 backend, we can "spontaneously catch" some of the short-channel-id updates if it happens that a block matches the filter we request.
Aargh. The hard problem is the SCID lookup!
darosior commentedon Jul 24, 2020
I think it's cleaner for us to make a (backward compatible) breaking change at this point.
Bitcoin backend, revived
lightningd
agnostic of its Bitcoin backend, and thus a Bitcoin backend extensible without complexifyinglightningd
.getrawblockbyheight
.hash
part for the topology updategetoutput
for requesting an output (no matter its nature,scid
- wallet rescan - channel funding)"backend_version"
field in the manifest sent by the plugin for compatibility."version": 1
. Iflightningd
sees version1
, it uses the new protocol.This also means we can upgrade the bakend version later for e.g. notification support.
getmanifest
does intentionally not makelightningd
cringe).bitcoind
and cache blocks in RAM (we can have a, say, 10-blocks cache). We can optimize the behaviour afterwards, and in the meantime we can do some nice smart thing in alternative Backend plugins. We can also have a--neutrino
startup option which translates in--disable-plugin bcli --plugin bip158_backend
later.AmkG commentedon Jul 24, 2020
We need to be aware of reorgs, especially those that take, say, 3 or 4 blocks, and which might have conflicting transactions affecting our wallet or channels, so I think this is mildly undesirable to just depend on a count rather than a header chain.
darosior commentedon Jul 24, 2020
Yes, sure (I meant
getrawblockbyheight
but without thegetrawblock
part). Edited to be explicit.Also, an even simpler upgrade mechanism: if a plugin registers
getoutputwhatever
thenlightningd
switch to the new behaviour, otherwise it fallbacks to parsing the raw blocks.ZmnSCPxj commentedon Jul 25, 2020
We also expose a
getutxout
method. This is used in theutxoset
/ scid tracking, but also used in thedev-forget-channel
implementation to check the UTXO of the channel being forgotten is still unspent.Now, for scid tracking, I believe what we are now planning is to let the plugin handle this by implementing a
getutxobyscid
and doing all the block parsing in the plugin.However, for
dev-forget-channel
this is not appropriate. One use-case fordev-forget-channel
is a funding transaction never confirming because weird blockchain reasons, so there is no scid. We can implement agetutxoutv2
that accepts an additionalscriptpubkey
argument.Then
getutxoutv2
is invoked only indev-forget-channel
(the only other current use ofgetutxout
is in scid tracking, which we want to give over to the plugin side completely)..This requires matching the scriptpubkey to downloaded Neutrino filters, and (re-)download the appropriate blocks that match it to see creation and spend events. This is potentially an expensive operation, but if it is triggered only indev-forget-channel
then it should be acceptable.Alternately we can just remove the
dev-forget-channel
checks, it could just check if the channel has an scid and if so it usesgetutxobyscid
, or if it does not have an scid,dev-forget-channel
does not do any further checks.dev-forget-channel
is a "big boy developer" command, so maybe acceptable?ZmnSCPxj commentedon Jul 27, 2020
@darosior @cdecker @niftynei @rustyrussell I have updated the original post with the current plan I have.
10 remaining items
ZmnSCPxj commentedon Jul 31, 2020
RBF is difficult for us to handle in
fundchannel
case: we need a stabletxid
. We could go with CPFP+RBF, so thatfundchannel
txid
is stable, but that means we cannot do afunchannel "all"
to put all available funds into a channel, since there would be no way to CPFP+RBF the resultingtxid
.And until we fully implement anchor commitments, we need a decent feerate estimation for
update_fee
message, since we cannot CPFP the unilateral close transaction (we can only CPFP+RBF it once we implement anchor commitments fully). I guess it is an argument to go anchor commitments.... sigh.Saibato commentedon Jul 31, 2020
yup, we are with CPFP ,RBF or good estimation in the hand of the miners and
signed tx have ever only a probability we like to influence to find its way out of mempool and then to not get reorged out.
Its a bit like programming Grover like oracles.
That's my overall reasoning to dice at some point and let s** happen, instead of central point of johoe...,
anchor commitments, hmm I process mempool message please wait.., please wait.., please wait .. ?
At some point you have to dice again to stop the wait loop or I guess you have thought about that, but i have not present how you mitigate this?
And also we have howto calc closing fee in spec with a MUST not SHOULD so
sigh sigh ...
ZmnSCPxj commentedon Aug 2, 2020
Okay, here is a horrible idea for fee estimation with a BIP158 backend:
inv
fortxid
s in their mempools, if my understanding of the P2P protocol is correct.darosior commentedon Aug 2, 2020
FWIW you could also use the
mempool
p2p message (if i remember its name correctly), but in this case you... basically trust your peers with your security model (timely confirmation of transactions) ?.. I think it's a terrible idea, and in any case a SPV lightning node needs to trust some "one" to give it reliable fee estimates so i'm not sure it should be shipped by default ?ZmnSCPxj commentedon Aug 2, 2020
SPV is certainly not something I would consider as "safe to ship by default", even if we had a perfectly trustless method of fee estimation.
FWIW even a tx-broadcasting fullnode will be asking all its peers for all txes in the peer mempool anyway, and thus trusting them to not lie about their mempools; we are simply reducing the bandwidth use by doing a random sampling that we assume to be representative of the entire data set, which is good enough for scientific papers anyway so should be good for us here in engineering-land (yeah right).
And if we can fully move to anchor commitments, then we can get away with a lousy lowball initial estimate and just CPFP+RBF later.
For example, a
withdraw
orfundchannel
transaction might have an anchor output (but only a single one, controlled by us) if there is no change output, and we can then later CPFP+RBF via either a change output, or an anchor output, if the initial fee estimate turns out to be too low.ZmnSCPxj commentedon Aug 3, 2020
lightningd
is schizophrenic. We havestruct txwatch
which watches specific transactions,struct txowatch
which watches txos, which we seem to use primarily foronchaind
andpeer_control
; these components do not have their own sub-object, but instead usestruct lightningd
. Then we havestruct txfilter
, which we use to look for receives into the onchain wallet, and is stored instruct lightningd
. We havestruct outpointfilter
(distinct fromstruct txowatch
!) that we use to look for spends from our wallet, but is stored instruct wallet
. Finally,gossip_control
needs to inform about channel spends togossipd
so thatgossipd
can destroy channels from the routemap, but it just implicitly trusts that every block will be downloaded and passed through it.We currently get away with this because we always get every transaction in every block, so even if our filters are schizophrenic it does not matter.
Anyway, the changes needed are:
txowatch
needs no changes.txwatch
needs to provide a scriptpubkey that the transaction pays out to, or an input the transaction spends. Thewatch_tx
interface needs no changing (it can extract either from the givenbitcoin_tx
) but thewatch_txid
needs one or the other. A scriptpubkey paid to by the transaction seems to be best;txwatch
is used by channel funding to determine if we have locked in. Still have to study how the "unconfirmed closeinfo utxos" work though.txfilter
needs no changes in interface. However we need a way to easily collect all scriptpubkeys stored in it; the hash table comments say iterating over it isn't efficient, but shrug maybe it is not too bad?outpointfilter
needs to include ascriptpubkey
for the utxo as well.gossip_control
needs to monitor and track utxos which succeed in gossipdget_txout
queries, and include those UTXOs when passing filters togettxesbyheight
.Another thing to consider is to add a
waitblockheightbackend
. For Electrum backends we can subscribe to header chain updates to complete calls towaitblockheightbackend
; this is more bandwidth-efficient since Electrum clients do not spam the server with repeated queries about the blockheight, the Electrum server does the polling in its local bitcoind (which is presumably faster since it is connecting locally).bitcoin-cli
backends can pollgetblockcount
. BIP-158 backends would periodically poll peers, or if somebody (a miner?) force-pushes a block at us then it wouldn't need to poll at all.The issue is back-compatibility to the v1 backend interface. Ideally the backend plugin should just implement
waitblockheight
directly, but if we are using a plugin with v1 backend, we need to implement the polling locally and thenwaitblockheight
on top of that. To emulatewaitblockheightbackend
for v1 we would need to pollgetrawblockbyheight
, but since thechaintopology
loop will do a largewaitblockheight
-gettxesbyheight
loop, we should cache thegetrawblockbyheight
result, else alternative backend v1 plugins might get repeatedly spammed bygetrawblockbyheight
. Hmm.ZmnSCPxj commentedon Sep 28, 2020
Add pruning label.
The reason we cannot work well with pruning is that we need historical information on channel opens, especially on a fresh install which knows nothing about older channels. Without pruning we just look up the old blocks and check the channel exists. With pruning we cannot validate historically long-lived channels, and cannot send it as gossip and cannot rely on it for routing.
A possible solution for pruning would be for us to process blocks at the block tip as usual from the fullnode, but for historical channels below the pruning cutoff, ask a server (e.g. Electrum servers have a decent interface for querying historical channels by SCID).
This is still less desirable than running a non-pruning fullnode, since you are trusting that the server does not lie to you for map data, and if you are e.g. evaluating map data for where best to make channels (i.e. autopilot) then the server can lie to you and bias your selection towards surveilling nodes.
cdecker commentedon Sep 29, 2020
The best a server can do is to not return a block, if we verify the Merkle Root and the blockhash against the local pruned node, which still maintains a list of all valid blockheaders in the past. Refusing to return a block is a pretty obvious failure, so I feel pretty good about anchoring our trust in a pruned node, and using the server to get the details in a tamper proof manner 🙂
ZmnSCPxj commentedon Oct 2, 2020
I was thinking in terms of Electrum
blockchain.transaction.id_from_pos
, which would be fairly cheap compared to downloading an entire block (though admittedly downloading an entire block is cached onlightningd
so we can afterwards validate every scid in it). But it seems that the same validation you mention can be done as well with that.blockchain.transaction.id_from_pos
returns a txid and optionally a merkle tree proof, we can validate the merkle tree proof against our local pruned node block header.However, with the Electrum
blockchain.transaction.id_from_pos
interface, the server can claim that the given transaction index is out of range (i.e. the block is an empty block), and cannot provide a proof of this assertion. What a node can do would be to binary-search the transaction index until it can locate the last transaction index that the server will admit as existing on that block, validating the merkle tree proof of every "found" transaction, and checking that the last transaction is indeed at the "rightmost" position of the merkle tree (I think this is possible, my naive reading of BIP180 suggests it is always possible to prove how many txes are in a block, it is only that the Electrum interface does not return that proof, but the details of the merkle tree might prove me wrong). Or maybe just download the entire block at that point, that seems like a lot of work and round trips.