Serve BIP 157 compact filters (p2p)

https://github.com/bitcoin/bitcoin/16442

Host: pinheadmz

Notes

  • Recall from the review club meeting for PR #15845 that we discussed BIP 158 which defines compact block filters.

  • BIP 157 is the proposed specification for requesting and sending compact filters between nodes on the p2p network.

  • This PR is an implementation of BIP157, such that full nodes with filter indexing enabled can serve those filters to peers that ask nicely :-)

Questions

  1. Did you review the PR? Concept ACK, approach ACK, tested ACK, or NACK? (Don’t forget to put your PR review on GitHub.)

  2. How does a node signal to the network that it can serve compact block filters?

  3. What is the behavior of a node that has not finished generating and indexing compact filters for every block in the chain?

  4. Does this PR expose any new denial-of-service attack vectors? If so, how are they being handled? Could this be improved?

  5. What is the purpose of the cfcheckpt message?

  6. Can Bitcoin Core be used to retrieve compact block filters from another peer?

  7. How is the test coverage in this PR? Do you think it could be improved, and how? Are there other ways to test this PR outside of the usual testing framework?

Meeting Log

13:00 < jnewbery> #startmeeting
13:00 < jnewbery> hi
13:00 < pinheadmz> hi
13:00 < fjahr> hi
13:00 < amiti> hi
13:00 < sanoj> hi
13:00 < emzy> Hi
13:00 < jnewbery> hi folks. pinheadmz is hosting today's meeting. Notes and questions are in the usual place: https://bitcoincore.reviews/16442.html
13:01 < pinheadmz> Hi all, is this anyone's first review club meeting?
13:01 < michaelfolkson> Hi
13:01 < jnewbery> Thanks to pinheadmz for writing notes and hosting. I'll hand straight over to him
13:01 < jimpo> hi
13:01 < miketwenty1> This is my first meeting
13:02 < nobody123> hi
13:02 < andrewtoth> hi my first meeting
13:02 < pinheadmz> oh thanks jimpo for attending - he's the author's of todays PR
13:02 < fanquake> hi
13:02 < jkczyz> hi
13:02 < tuxcanfly> hello
13:02 < pinheadmz> Ok so for new friends: don't be afraid to ask questions! at any time, we'll get to you.
13:03 < pinheadmz> and thanks tuxcanfly for attending, he's working on bip157/158 in bcoin and knows a little something about cfilters
13:04 < pinheadmz> Cool, so does someone want to summarize the PR in a few lines?
13:04 < fjahr> It makes the filters from bip157/158 available on the P2P layer
13:05 < pinheadmz> fjahr: yes!
13:05 < pinheadmz> so bip158 filters are a compact way to represent the contents of a block
13:05 < jonatack> hi
13:05 < jonatack> from the bitcoiner meetup in Paris :)
13:06 < pinheadmz> and bip157 is the specification on how those filters can be requested and delivered around the netwrk
13:06 < pinheadmz> jonatack: ooh lala!
13:06 < pinheadmz> Did everyone get a chance to review the PR? How about a quick y/n from everyone
13:06 < pinheadmz> maybe tell us how far you got in review?
13:06 < jnewbery> for those who don't have time to read the full BIPs, here's a summary of what they do: https://bitcoin.stackexchange.com/a/86232/26940 :)
13:06 < jimpo> y
13:06 < michaelfolkson> y
13:07 < jnewbery> y. high-level review. Didn't do detailed review
13:07 < tuxcanfly> y
13:07 < jkczyz> y - most code but didn't get through the tests
13:07 < fjahr> y, had looked at it a while back, did it again this week, wish I had more time for testing
13:07 < fanquake> n
13:08 < amiti> ish- took a look but didn't dig in
13:08 < pinheadmz> Great! Lets go through the questions -- how does a node signal to the network that it is able to serve CFilters ?
13:08 < miketwenty1> n
13:08 < pinheadmz> michaelfolkson: do you wanna take this one? ^
13:08 < emzy> n
13:08 < fjahr> there is a service bit signalling bip158 support
13:08 < andrewtoth> n
13:08 < andrewtoth> NODE_COMPACT_FILTERS service bit
13:09 < michaelfolkson> Signals NODE_COMPACT_FILTERS yup
13:09 < miketwenty1> cfilters = compact filters?
13:09 < pinheadmz> miketwenty1: yes
13:09 < jnewbery> (it's initially slightly confusing that that's defined in BIP 158)
13:10 < pinheadmz> miketwenty1: bip158 defines cfilters or compact filters or casually "neutrino filters"
13:10 < jimpo> jnewbery: Yeah... I agree :-/
13:10 < miketwenty1> thanks pinheadmz
13:11 < pinheadmz> does anyone NOT understand what cfilters are or why they are important? maybe we should check in on that
13:11 < nobody123> is there a class which handles all the p2p networking?
13:12 < pinheadmz> nobody123: the net_processing.cpp file is where most of the messaging logic is handled - is that what you mean?
13:12 < jnewbery> nobody123: not really a class. Look in net.cpp for the low-level peers and connections management, and then in net_processing.cpp for the application-level logic
13:13 < nobody123> jnewbery, pinheadmz thx
13:13 < pinheadmz> Ok, so on the topic of the service bit -- Did anyone notice the sort of open-ended question about this service bit as it relates to this PR?
13:13 < pinheadmz> (hint: question #3)
13:14 < sanoj> in terms of how to do this without blocking net threads and not signal to peer peers until the indexing is in sync?
13:14 < pinheadmz> sanoj: yes!
13:14 < pinheadmz> As far as I understand, the service bits are all set on launch and aren't really designed to ever change during run time
13:14 < pinheadmz> jimpo: is that correct? ^
13:15 < sanoj> so as is if block filter index is still syncing then we don't advertise the service bit (https://github.com/bitcoin/bitcoin/pull/16442/files#diff-9a82240fe7dfe86564178691cc57f2f1R2657)
13:15 < jimpo> yes
13:15 < pinheadmz> sanoj: right - so what happens once a node has finished indexing? Can it start serving filters right away?
13:15 < jimpo> this would be the first case of a dynamically changing service flag (if that idea makes it through) review, hence the discussion
13:15 < andrewtoth> From jimpo in the PR: "And if I understand correctly, we re-advertise our local address with service bits to each peer every day on average, so the network should see the change eventually without the node operator having to do anything." Where does this happen?
13:15 < headway-translat> pinheadmz: purpose of compact filters is to create a space-efficient proof of txns non-inclusion in a block so that light clients have cheaper access to truth.  e.g. lightning light clients can space / bandwidth efficiently certify that channel closure txns etc aren't in blocks
13:16 < jnewbery> my point was that service bits should advertise *capabilities*, not *current node state*. That's my understanding at least
13:16 < jimpo> andrewtoth: looking
13:16 < pinheadmz> headway-translat: great summary, yes
13:16 < pinheadmz> jnewbery: thats a good point
13:17 < jnewbery> the service bits are somewhat long-lasting. They're gossipped in VERSION messages, and served on DNS seed responses I believe
13:18 < jimpo> andrewtoth: From my reading AdvertiseLocal + nNextLocalAddrSend
13:18 < jnewbery> so if I start by telling my peers that I don't support a feature, that'll get gossipped around the network for some time after I change my service bit flags
13:18 < andrewtoth> jimpo: thanks
13:18 < pinheadmz> I suppose the question for light clients would be: if you request a cfilter and the node respnds with notfound or null -- is that reason to disconnect or ban? Because that node could still just be syncing
13:18 < jimpo> jnewbery: My argument would be that that's blurry. A node is incapable of serving filters it hasn't indexed yet.
13:20 < andrewtoth> How can a light client verify that the cfilters are correct?
13:20 < michaelfolkson> So jamesob makes the comparison with IBD, is how that is dealt with instructive here?
13:21 < fjahr> andrewtoth: the client can compare cfilters from different nodes
13:21 < pinheadmz> andrewtoth: good question - until the cfilters are committed in the block somehow (and therefore verified by all network nodes and ensured by proof of work) - there is no way to know without checking the block yourself
13:22 < jnewbery> jimpo: am I right in saying your main objection is that it makes logic more complicated for clients: https://github.com/bitcoin/bitcoin/pull/16442#discussion_r336731315 ?
13:22 < pinheadmz> andrewtoth: fjahr: but yes, BIP157 specifies a process by which a light node can be "pretty sure" the filters they get are correct
13:22 < jimpo> pinheadz: Even checking the block yourself is insufficient. You'd need chainstate or undo data. :-/
13:22 < michaelfolkson> You could have two flags. One saying that you intend to provide them and one saying you're not ready to provide them? After a certain period of time the gossip would ignore the second flag?
13:22 < pinheadmz> jimpo: interesting - why would that be? aren't the cfilters noncontextual ?
13:22 < jimpo> jnewbery: Yes, that is my concern. That allowing a valid case where a node advertises that it can serve filters and is unable to would complicate client logic.
13:23 < jimpo> pinheadmz: There was a change to the BIP last year that made them contextual. Specifically, the filters include the prevout output scripts, which are not included in blocks, only referenced by inputs.
13:23 < Talkless> hi, are these filters are against txids, scripts (addresses), both?
13:23 < jimpo> just scripts. Block output script and input (prev output) scripts.
13:24 < Talkless> thanks
13:24 < pinheadmz> jimpo: wow ok! extra work for the node... tuxcanfly did you know that? lol
13:24 < jnewbery> I think you need that logic anyway. In an untrusted p2p network, you don't know who is advertising that they can serve the filters: malicious nodes, buggy implementations, stalling node, unreliable connections, ... A BIP 157 client has to be able to handle cases where it gets invalid or missng reponses
13:24 < andrewtoth> michaelfolkson: what would be the difference to just signaling the first bit and then disconnecting after the ignore time time if they don't start providing?
13:25 < jimpo> jnewbery: Sure, but you just ban. This is a case where you need to disconnect from a *properly behaving* node.
13:26 < pinheadmz> jnewbery: youre thinking the signal bit should always be active, and if a node can't reply with a filter, they get banned by the client?
13:26 < Talkless> jimpo: but that's only about tx'es inside actual block, transactions queued in mempool will not be delivered through this mechanism to the light wallets?
13:26 < sanoj> jimpo: what about a state that returns some sort of sync message. I wouldn't imagine it'd happen that often but it would avoid the disconnect for proper behavior and maintain the validity of the capabilities service bit
13:26 < jimpo> Probably what a client should do is fetch the cfcheckpts to see how in-sync the node is
13:26 < pinheadmz> Talkless: thats my understanding
13:26 < jimpo> if nodes advertise the service bit before they have an in-sync index
13:27 < jnewbery> A bit more on the contextual/non-contextual stuff: the new scripts created in the block can obviously be constructed from the block. The scripts spent in the block require a node that has the UTXO set or the 'undo data' for the block
13:27 < pinheadmz> Talkless: wasabi wallet, for example, uses neturino filters but ALSO connects to Tx relay to discover "0-conf"
13:27 < Talkless> pinheadmz: interesting.
13:27 < jnewbery> ('undo data' == outputs spent in a block)
13:28 < michaelfolkson> andrewtoth: I suppose it depends whether you are concerned about maintaining connections or not. If you're not then sure advertise that you offer something and get disconnected when you disappoint
13:28 < miketwenty1> undo data = data needed in case of a reorg?
13:29 < pinheadmz> miketwenty1: yep, the coins spent by a block - so they are "unspent" if a blockis disconnected
13:29 < michaelfolkson> I would think you would generally want to minimize disconnections when both parties are honest and responsive
13:29 < pinheadmz> I like jimpo idea that light clients should request cfheckpts first to determine how in sync a node is
13:30 < pinheadmz> then the service bit could stay on, like jnewbery said to "indicate capability"
13:30 < jimpo> just... but why?
13:30 < jimpo> like I don't understand why dynamically switching on a service flag is such a bad idea
13:31 < Talkless> will new light wallet have to download all filters from the beginning, or could start from it's own "birthday" if wanted?
13:31 < jimpo> if it could flap, sure that's not good. but it's just a one-way transition from off to on.
13:31 < miketwenty1> just a clarification q.
13:32 < miketwenty1> pinheadmz "checkpts"?
13:32 < pinheadmz> jimpo: I was wondering about the tiny amount of time it would take a fully synced node to produce the cfilter... there could be a moment where even a synced node can't provide the filter for the tip ?
13:32 < andrewtoth> Talkless: I believe this PR introduces a way to request a range of filters, so it can start at birthdate
13:32 < jnewbery> I personally don't like it for a couple of reasons: service bits are handled by our net layer, and I don't like coupling together net with the compact blocks index. second: I think there's a general assumption that service bits don't change and I'd prefer not to break that
13:32 < jimpo> nope, we block on the background thread in that case
13:32 < jimpo> until the filter is built
13:32 < pinheadmz> jimpo: πŸ‘
13:32 < headway-translat> Talkless CMIIR but i believe how the encoding works, to have higher degree of soundness, client would need to request filters several blocks before their bday
13:32 < pinheadmz> miketwenty1: so we'll get to checkpts i none sec...
13:33 < headway-translat> CMIIW*
13:33 < pinheadmz> s/in one...
13:33 < Talkless> andrewtoth: thanks
13:33 < Talkless> headway-translat: interesting, any ideas why?
13:34 < pinheadmz> headway-translat: Talkless: There is also the filter-headers, which commit to the previous block
13:34 < headway-translat> Believe it's due to how the filter headers work but I could be wrong. https://bitcoin.stackexchange.com/questions/86231/whats-the-distinction-between-bip-157-and-bip-158-are-they-supported-by-bitcoi/86232#86232
13:35 < pinheadmz> IIUC, both filter headers and checkpoints are needed just because we can't commit the filter the in the block yet
13:35 < headway-translat> yeah I suppose I am combining the two proofs
13:35 < headway-translat> erroneously
13:35 < andrewtoth> jnewbery: re service bits not changing, what is relying on this assumption?
13:36 < pinheadmz> lets talk about the checkpoints - does anyone want to explain the purpose of the cfcheckpt message ?
13:36 < michaelfolkson> andrewtoth: I think jnewbery answered this earlier. The incorrect service bit would percolate around the gossip network and be a pain to replace
13:37 < jimpo> well, let's be clear. A service bit can change on a node -- it just requires a restart currently.
13:37 < jimpo> and still does take a while for the new setting to propagate.
13:37 < andrewtoth> yes, that's what I was thinking, a restart would change and gossip would still be out of date
13:38 < jimpo> the difference here is that a service bit cannot (yet) change over the lifetime of a *connection*
13:38 < pinheadmz> tuxcanfly: are you still with us? do you want to explain the cfcheckpt message?
13:38 < jnewbery> andrewtoth: when peer addresses are advertised in ADDR messages, that peer's service bits are included, so you can think of other nodes caching the peer's service capabilities
13:39 < jnewbery> jimpo: right. If you consider switching nodes off/on then anything can change!
13:39 < miketwenty1> q. are checkpoints ephemeral or forever?
13:40 < miketwenty1> im not clear on the purpose of the checkpoint
13:40 < jkczyz> pinheadmz: the checkpoints allow clients to parallelize getcfilters from peers
13:40 < miketwenty1> in the context of this BIP
13:40 < pinheadmz> jkczyz: ty! yes
13:40 < jnewbery> miketwenty1: https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#getcfcheckpt
13:40 < pinheadmz> miketwenty1: so thats an interesting q, because the checkpoints are based on 1000-block intervals from the requested "stop" block
13:41 < pinheadmz> so its possible that every time you make that request, you get a different series back
13:41 < pinheadmz> (jimpo right?)
13:41 < jimpo> no, 1000-block intervals from the start block
13:41 < pinheadmz> sorry thanks
13:41 < jimpo> so they are the same on every request so they can be cached easily
13:41 < pinheadmz> https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#getcfcheckpt
13:41 < pinheadmz> just mentions the stophash ...?
13:42 < jimpo> the start block is 0
13:42 < pinheadmz> ah ok I see in https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#cfcheckpt yes
13:43 < pinheadmz> So if I'm a brand new light client, what order do I send the BIP157 messages in?
13:43 < pinheadmz> (hint: https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#client-operation)
13:44 < tuxcanfly> pinheadmz: sorry afk a bit, but yeah I remember coming across getcfcheckpt as a way to sync cfilters in batches
13:44 < tuxcanfly> because earlier you had to make a request per block... for each cfilter!
13:44 < jimpo> eek, the client-operation in the BIP is still out of date and assumes the old outpoint filter spec
13:44  * jimpo covers eyes
13:44 < pinheadmz> tuxcanfly: ty! a nice optimization
13:44 < jimpo> Need to finish re-writing that
13:45 < tuxcanfly> yeah you even had two types of filters so that would have been double the number of p2p messages for each client
13:45 < pinheadmz> jimpo: πŸ˜†
13:46 < sanoj> do i understand correctly that if we are syncing to block 600,001, the last checkpoint we'd receive covers up to 600,000?
13:47 < pinheadmz> sanoj: yes that sounds right, but then presumably when we request the actual filters (in parallel) we would get that last 1 as well
13:47 < sanoj> if so, what happens if there is a reorg that spans that 1000 filter header mark?
13:47 < pinheadmz> because the light client knows the block headers on the best-work-valid chain
13:49 < jnewbery> sanoj: 'checkpoints' might be a bit confusing terminology here because we also have checkpointed blocks in Bitcoin. Here, cfcheckpoints are just an optimization for downloading the full set of compact block filters for the chain
13:49 < miketwenty1> i fell into this group initially^
13:49 < pinheadmz> Ok home stretch! lets start wrapping up --
13:49 < jimpo> as a clarification, they help parallelize the download of the filter headers
13:50 < jimpo> then the filter headers help parallelize the download of the actual filters
13:50 < pinheadmz> Q#6 - does this PR enable bitcoin core to RETRIEVE filters from peers ?
13:50 < jnewbery> once a client has downloaded all the compact block filters initially, then checkpoints have no further function
13:50 < sanoj> i see. Thanks all.
13:50 < jnewbery> jimpo: right - thanks for the clarification!
13:51 < miketwenty1> so, @jn
13:51 < michaelfolkson> What was implementing Neutrino on Bcoin like pinheadmz? Did you consider similar issues? Anything you will revisit now it is (almost) fully implemented on Core?
13:51 < miketwenty1> jnewbery to answer @sanoj's question .. you would just undo data.. and redownload compact filters?
13:52 < pinheadmz> michaelfolkson: tuxcanfly might wanna field this one - so far we have bip158 implemented but actually not bip157 yet
13:52 < jkczyz> pinheadmz: Nope, with this PR it only sends filters. It does not request or handle receiving them
13:52 < michaelfolkson> pinheadmz: Oh interesting. Delayed because of Core timetable or just lack of resources?
13:52 < pinheadmz> jkczyz: thats right!
13:53 < jnewbery> miketwenty1: light clients are not keeping track of the UTXO set, so they don't need undo data to roll back during a reorg
13:53 < pinheadmz> We have the spec and the bip157 process, but since bitcoind is not intended to be a light client, it doesn't need to request the filters
13:53 < pinheadmz> (although in a former pr review club, we DID discuss how bip158 filters are useful internally for the wallet)
13:53 < jkczyz> jnewbery, jimpo: Peers can send notfound in response to getdata. Would something analogous be suitable for when the filters are not ready? Is there some general error mechanism in Core's P2P layer?
13:53 < jnewbery> they do need to keep track of confirmations, so they'd need to work out whether any transactions that they're interested in had been removed from the block chain during the reorg. That doesn't require undo data
13:54 < jimpo> jkczyz: I remember some discussion of getting rid of NOTFOUND. Where did that end up?
13:54 < jnewbery> jkczyz: I agree that NOTFOUND seems appropriate here
13:54 < jnewbery> jimpo: not that I'm aware of. Perhaps you're thinking of REJECT messages?
13:54 < jimpo> maybe
13:54 < pinheadmz> jimpo: i thought that discussion was about mempool leaks?
13:55 < tuxcanfly> michaelfol: to answer your bcoin question - it was pretty straightforward... however there were some bip updates which needed to be synced... the test vectors were helpful in that regard, to make sure we're core compatible for every edge case
13:55 < pinheadmz> er, nm - i might be confused
13:55 < fjahr> could also implement something like NOTREADY?
13:55 < jnewbery> NOTFOUND could be useful in cases like  https://github.com/bitcoin/bitcoin/pull/15505 , so I hope they don't get removed!
13:55 < fjahr> to make sure it is clear why it was not found
13:56 < pinheadmz> michaelfolkson: tuxcanfly actually had to re-write the PR! This is the one that landed, if you're interested: https://github.com/bcoin-org/bcoin/pull/797
13:56 < pinheadmz> OK five minutes left !!
13:56 < tuxcanfly> michaelfol: the p2p cfilter serve was part of the PR but didn't enough review so it isn't merged just yet... we will be getting that one soon
13:56 < pinheadmz> Did anyone play with the tests at all? Or try anything wacky with an actual lightning node?
13:56 < pinheadmz> I ran the test suite with wireshark open, sniffing loca host - and is really cool to see the dialog between the test nodes
13:57 < pinheadmz> *localhost
13:57 < tuxcanfly> I just have one question to jimpo or anyone interested
13:57 < jimpo> sorry, guys I have to go now
13:57 < jimpo> thanks everyone for you time reviewing!
13:57 < pinheadmz> jimpo: TY so much for attending! <3
13:57 < jonatack> thanks jimpo
13:57 < jnewbery> jimpo: thanks so much for dropping in and answering questions!
13:57 < miketwenty1> (y)
13:57 < fjahr> I just inspected some messages by logging, nothing much
13:58 < pinheadmz> tuxcanfly: whats your Q ?
13:58 < Talkless> thanks jimpo!
13:58 < jonatack> met laurent mt here and we've been discussing and following along
13:58 < tuxcanfly> ah well... I was just curious if more thought was given to committing cfilters to blocks via coinbase (something similar was mentioned in the btcd/bitcoind comments)
13:59 < fjahr> I thought for a moment the filters in the test might be all 0's because there are no transactions in the blocks but forgot about the coinbase of course...
13:59 < Talkless> <andrewtoth> Talkless: I believe this PR introduces a way to request a range of filters, so it can start at birthdate
13:59 < Talkless> I wonder this range is defined w.r.t block number, or UTC..?
13:59 < Talkless> if new light wallet is created, how does it define it's birthday?
14:00 < pinheadmz> Talkless: the day it generated the seed :-)
14:00 < Talkless> if it's block number, it has to check.. what..? Ask node whats latest block..? at that time..?
14:00 < Talkless> so the time?
14:00 < pinheadmz> yeah or block height would work
14:01 < jnewbery> Talkless: either is fine. If you generate a new private key randomly, you know for sure that there haven't been any sends to that key before that time
14:01 < pinheadmz> just to know at what point in the past history there will definitely not be any incoming transacations (bc the addresses didnt esit yet)
14:01 < Talkless> well if wallet knows time, and p2p services would need block indexes, some translation would be needed.
14:01 < jnewbery> whether you store that as a clock time or block number is an implementation detail
14:01 < pinheadmz> jnewbery: do you know how the discussion ended up on the mailing list re: commiting filters to the block? I know it was proposed...
14:01 < Talkless> jnewbery: my question is how would you ask a node a block filters since your birthday? Will node need time, or block index?
14:02 < jnewbery> pinheadmz: I haven't seen any discussion of that recently. I think we'd want non-committed BIP157 filters to be used for a long time before proposing a soft fork to commit to them
14:02 < pinheadmz> Talkless: you provide a start height
14:02 < pinheadmz> https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#getcfilters
14:03 < pinheadmz> jnewbery: makes sense, might need updates to the protocol before making it consensus
14:03 < Talkless> pinheadmz: ok so if wallet knows UTC time of it's "birthday", how could it transalte it's UTC birthday to the height?
14:03 < pinheadmz> Talkless: ha, ok I guess block height is more relevant in this case :-)
14:03 < pinheadmz> although timestamps are commited in the block headers as well
14:03 < jnewbery> Talkless: sync all block headers and look at timestamps (and then add some buffer)
14:03 < pinheadmz> Ok that about does it!
14:03 < miketwenty1> is this the part of the meeting where you let the new guy merge the PR?
14:03 < pinheadmz> Anyone have any lingering questions or comments?
14:04 < pinheadmz> miketwenty1: πŸ˜‚
14:04 < jnewbery> #action ship it
14:04 < Talkless> jnewbery: how much all block headers take space?
14:04 < Talkless> bandwth?
14:04 < pinheadmz> Talkless: headers are 8 bytes x ~600,000 blocks in the chain
14:04 < pinheadmz> *80 bytes sorry
14:04 < Talkless> 80 yeah
14:04 < Talkless> right, forgot that
14:04 < Talkless> simple math
14:05 < pinheadmz> ok im calling it!
14:05 < pinheadmz> #endmeeting
14:05 < pinheadmz> thanks everyone for hanging out!
14:05 < emzy> ca. 50 MB
14:05 < jnewbery> Thanks for hosting pinheadmz. Great job!
14:05 < emzy> about*
14:05 < tuxcanfly> thanks pinheadmz
14:05 < Talkless> thanks!
14:05 < michaelfolkson> Thanks pinheadmz jnewbery et al
14:05  * pinheadmz headbanging \m/
14:05 < andrewtoth> thanks!
14:05 < emzy> tnx!