Avoid mixing different `OutputTypes` during coin selection (wallet)

https://github.com/bitcoin/bitcoin/pull/24584

Host: josibake  -  PR author: josibake

The PR branch HEAD was 31895fb at the time of this review club meeting.

Notes

  • A bitcoin transaction will often have a payment output and a change output. In order to preserve transaction privacy and avoid leaking information about a user’s wallet and funds, we want to keep the payment address and payment amount as private as possible. In other words, we don’t want to leak information which allows an outside observer to guess which of the two outputs is the payment vs the change.

  • One technique used for determining the payment address and amount is the “Payment to different script type” heuristic. This allows an outside observer to guess the payment address and amount with reasonable accuracy for certain types of bitcoin transactions.

  • PR #23789 added payment address matching when generating a change address as a means of breaking the heuristic. This logic can lead to the wallet having UTXOs of different address types (e.g bech32m, bech32, P2SH, legacy). Depending on how these UTXOs are spent in the future, they might still leak information about which is the change/payment address in the original transaction.

  • PR #24584 adds logic to avoid mixing different address types when selecting UTXOs to fund a transaction.

Questions

  1. Did you review the PR? Concept ACK, approach ACK, tested ACK, or NACK?

  2. In your own words, what is the “Payment to different script type” heuristic? How does it work?

  3. What are OutputTypes and TxoutTypes? How are they different?

  4. How can spending mixed OutputTypes in a later transaction reveal information about the first transaction?

  5. How do we pick the “best” SelectionResult? (HINT: what is the “waste metric”)

  6. Are there other things besides waste that we could consider when funding a transaction?

  7. We run over each OutputType in AttemptSelection, but are there other places we could have applied the “no mixing” logic?

  8. What is the “erase/remove” idiom? Why is it preferred over other methods of removing elements from a container?

Meeting Log

  117:00 <josibake> #startmeeting
  217:00 <glozow> hi
  317:00 <josibake> hi everyone!
  417:00 <vnprc> hi
  517:00 <josibake> welcome to this edition of the pr review club
  617:00 <ls55> Hi
  717:00 <lightlike> hi
  817:00 <antonleviathan> o/
  917:00 <furszy> hi
 1017:00 <josibake> we are reviewing #24584, "avoid mixing different `OutputTypes` during coin selection"
 1117:01 <theStack> hi
 1217:01 <josibake> notes can be found here: https://bitcoincore.reviews/24584
 1317:01 <Bayer> Hi
 1417:01 <josibake> anyone's first time?
 1517:01 <Bayer> Yep!
 1617:01 <antonleviathan> same here
 1717:01 <vnprc> 2nd
 1817:01 <furszy> same
 1917:01 <josibake> welcome Bayer, antonleviathan, furszy!
 2017:02 <Bayer> ty:)
 2117:02 <josibake> for the first timers, feel free to blurt stuff out! no need to ask if a question is relevant or on-topic before asking
 2217:02 <Murch> Hi
 2317:02 <antonleviathan> sounds good, I want to thank Murch for inviting me!
 2417:02 <josibake> did everyone get a change to review the PR or read over the notes? you can respond with a quick y/n
 2517:02 <svav> Hi
 2617:02 <effexzi> Hi every1
 2717:02 <antonleviathan> y
 2817:02 <ls55> y
 2917:02 <Bayer> Y
 3017:02 <furszy> y
 3117:03 <a1ph4byte> Hello.
 3217:03 <vnprc> y notes, n review
 3317:03 <Murch> y
 3417:03 <svav> y notes
 3517:03 <larryruane> hi
 3617:03 <schmidty> hi
 3717:03 <effexzi> Y notes
 3817:03 <josibake> awesome, lots of y's :)
 3917:04 <josibake> can someone summarize what this PR is about?
 4017:04 <Murch> Privacy!
 4117:05 — Murch might have been too concise :p
 4217:05 <vnprc> improving the privacy of core wallet coin selection
 4317:05 <josibake> murch: you win the "briefest summary award"
 4417:05 <antonleviathan> to help reduce the ability of observers to infer what the change address is, and move towards newer UTXO types over time when sending txs
 4517:05 <svav> Preventing privacy leaks caused by mixing output types
 4617:06 <josibake> vnprc, antonleviathan, svav: all correct!
 4717:06 <josibake> for those that reviewed the notes/PR, Concept ACK, approach ACK, or NACK?
 4817:06 <ls55> Approach ACK
 4917:07 <Bayer> Concept ACK
 5017:07 <Murch> Homogenizing input usage to not reveal that a wallet deals in multiple output types while being cost sensitive about it
 5117:07 <larryruane> string concept ACK
 5217:07 <Murch> Approach ACK
 5317:07 <larryruane> *strong
 5417:07 <svav> josibake what made you feel this PR was a necessity?
 5517:07 <effexzi> Concept ACK
 5617:07 <vnprc> concept ACK
 5717:07 <josibake> svav: using bitcoin core as my primary wallet and not liking what i saw in the inputs of my transactions :)
 5817:07 <svav> josibake was there a particular incident? Where did you get the idea for the PR from?
 5917:08 <antonleviathan> Approach ACK
 6017:08 <josibake> svav: from my own experience and also a data analysis project ive been working on! but i can answer more questions about that later
 6117:08 <josibake> can someone explain what the "payment to different script type" heuristic is?
 6217:09 <josibake> and why is it relevant to this PR ?
 6317:09 <ls55> It is a way of identifying which output is change and which is payment based on the type of input.
 6417:09 <josibake> ls55: correct! does it work on any transaction?
 6517:10 <Murch> When a transaction has two different output types, and one of the matches the type of the inputs, observers can assume that the matching output is the change
 6617:10 <lightlike> why does one, in the example of the PR, infer that an output that is being mixed later is likely the change of an earlier tx? If it was the payment instead, couldn't that later be be mixed with other outputs as well?
 6717:11 <Bayer> It does not work on all transactions. only ones with differing script types.
 6817:11 <Murch> If both outputs match type, but one of them is later mixed with more modern UTXOs on a transaction, we can assume that the other output was the one that picked the less modern format—and thus was the receiver.
 6917:11 <vnprc> josibake: no i think it requires a tx with multiple output address types, one of which matches the input type
 7017:12 <lightlike> ok, so the assumption is that whoever uses older formats doesn't support newer ones?
 7117:12 <Murch> lightlike: Right
 7217:13 <Murch> Because they cost more
 7317:13 <josibake> lightlike: great question. if i see a tx with all bech32 inputs and two p2sh outputs, and then in the next tx i see that p2sh output mixed with bech32 inputs to fund the second tx, it is very likely that the p2sh output being mixed was the change from the first, assuming that the wallet is picking a change address to match the payment address (which core does)
 7417:13 <Murch> If you could receive more modern formats, why would you request getting paid in an older, less-blockspace efficient format?
 7517:13 <Bayer> I think some users may accidentally use older formats out of ignorance no?
 7617:14 <josibake> Bayer, vnprc: yep, this is a specific type of tx. as of today tho, these txs are about 30% of all txs
 7717:15 <Murch> > <@Bayer:libera.chat> I think some users may accidentally use older formats out of ignorance no?
 7817:15 <Murch> * Well, if they consistently use older formats, they'd still match the pattern, wouldn't they
 7917:15 <Murch> Bayer: Well, if they consistently use older formats, they'd still match the pattern, wouldn't the@?
 8017:16 <antonleviathan> when you say "type" of tx, it's not really about the tx right, it's more about the script type of input and output utxos?
 8117:16 <vnprc> murch: commercial wallets and exchanges may hold back on adopting new address types due to poor support for these address types in the wallet ecosystem
 8217:16 <Murch> I think in this case it refers to transactions with mixed output types
 8317:17 <josibake> antonleviathan: almost yes! which is a great segue into the next question
 8417:17 <josibake> what are OutputTypes and TxoutTypes?
 8517:17 <Murch> vnprc: Sure, but they could still use it for the change in that case, since change only goes from their wallet to themselves
 8617:17 <josibake> how are they different?
 8717:17 <furszy> aside from the extra fee costs, wouldn't be more confusing for a chain analysis company if the software would be randomly changing output formats? instead of be always uniformly using the newest one or using the same provided by the receiver.
 8817:17 <larryruane> Murch's pinned tweet may be helpful: https://twitter.com/murchandamus/status/1262062602298916865?s=20&t=cM3sr6T5n7PrLnEnu5pM-w
 8917:17 <josibake> OutputType: https://github.com/bitcoin/bitcoin/blob/be7a5f2fc400e7a3ef72dedbdcf49dd6c96d4f9e/src/outputtype.h#L18
 9017:17 <Murch> But if they don't use it for change and also don't use it for receiving, they effectively just behave as if they only had access to the old type
 9117:18 <josibake> TxoutType: https://github.com/bitcoin/bitcoin/blob/be7a5f2fc400e7a3ef72dedbdcf49dd6c96d4f9e/src/script/standard.h#L59
 9217:18 <ls55> `TxoutType` is the scriptPubKey. Normally the `standard.cpp:Solver()` function is used to identify which type of a given `CScript& scriptPubKey`.
 9317:18 <ls55> `OutputType` is related to address format. It is also related to descriptors (`OutputType::LEGACY -> pkh()`, `OutputType::BECH32 -> wpkh()`, `OutputType::BECH32M -> tr()` and so on).
 9417:18 <ls55> Although both are similar in concept, the `TxoutType` enum also covers non-default transaction type, multisign and non-spendable OP_RETURN script.
 9517:18 <Murch> furszy: Yeah, there are some fun ways how you could deliberately break heuristics. ::
 9617:19 <josibake> furszy: great question. what would be the downside for the user if a wallet did this?
 9717:19 <josibake> ls55: great summary. so for our txs, we are referring to transactions that mix different address formats in the inputs
 9817:20 <theStack> josibake: furszy: one downside is that that this would also include old formats like P2PKH where you would pay more fees then for spending, i guess
 9917:21 <furszy> josibake: yeah, fees mostly and.. wallet size increase
10017:21 <vnprc> josibake: confusion, higher fees, just a lot more friction in general for the user
10117:21 <Murch> theStack: Yeah, added costs, and potentially causing yourself to need to mix inputs in a later transaction
10217:22 <furszy> vnprc: I don't think that the user would get any extra confusion.
10317:22 <josibake> theStack: yes! in this scenario, better privacy would cost the user more in fees
10417:22 <furszy> I mean, the change script is automatically handled by the walelt
10517:22 <furszy> *wallet
10617:22 — theStack dreams of a fantasy world in 10-20 years where simply everyone uses only P2TR and all those discussions are obsolete xD
10717:22 <furszy> most of the users don't even know how the change looks like
10817:23 <Murch> theStack: Hopefully ~5 years! :D
10917:23 <josibake> theStack: P2TR was one of the motivations for this PR! with P2TR adoption, i expect the pay to different script type heuristic to match even more txs as user transition from legacy, p2sh, bech32 to using bech32m
11017:23 <Murch> Although, who knows maybe someone will propose a new output type so we can do CISA and the whole process starts over
11117:23 <theStack> Murch: heh, that's optimistic for sure
11217:24 <josibake> furszy: good point, altho if a user wants to use a newer output type, it could be confusing/annoying if they see there wallet using lots of older, less efficient types
11317:24 <Murch> True
11417:24 <furszy> yeah for sure
11517:25 <Murch> It's been 4.5 years since segwit activated and only 50% of the inputs are segwit.
11617:25 <Bayer> That always surprises me! Is that just slow merchant/custodial wallet adoption?
11717:26 <furszy> probably there should be a balance, between the extra privacy (which comes with extra fee costs to confuse observers) and the "always use the most efficient payment type"
11817:26 <josibake> so we've touched on the topic of efficient vs private transactions already.. so how does the wallet pick the "best" input set for funding a tx?
11917:26 <furszy> maybe configurable by the user?
12017:26 <ls55> "50% of the inputs are segwit" Does this refer to the coins currently in the UXO Set?
12117:26 <josibake> more specifically, how do we choose the best result when running `SelectCoins`
12217:26 <vnprc> furszy: consider this scenario: a user spends down most of their funds leaving only old address types. They find themselves unable to spend funds that require a newer address type even though their wallet software tells them they have enough BTC. The user would need to consolidate UTXOs into a newer address type. This user doesn't understand why
12317:26 <vnprc> this is required.
12417:27 <ls55> The best `SelectionResult` is the one with the least waste or with the most inputs when wastes are equal.
12517:27 <Murch> ls55: No, to input types by count in the transactions we see per day: https://transactionfee.info/charts/inputs-types-by-count/
12617:27 <theStack> ls55: no, looking at the UTXO set the statistic is way worse; but that's also because many UTXOs are there forever, as they simply were abused for storing data in the early days
12717:27 <sipa> Part of it is a chicken-and-egg problem. Receiving wallets don't want to upgrade before mostly all sending software/sites supports it. Especially enterprise/custodial sending software/sites usually have their hands full support the latest dog breed variety ape coin, and won't allocate much engineering resources on bitcoin unless receivers demand it.
12817:28 <Murch> josibake: We currently use multiple different coin selection algorithms and then pick the one that scores best per the waste metric.
12917:28 <josibake> furszy: regarding a balance between privacy and efficiency, this is one of the things that makes coin selection hard (fun)
13017:28 <ls55> "abused for storing data " Cannot those coins be removed from the UTXO Set ?
13117:29 <antonleviathan> "select whether you prefer privacy or lower fees" :p
13217:29 <sipa> antonleviathan: If that's the choice, unfortunately there is little hope for privacy.
13317:30 <josibake> vnprc: what do you mean by "spend funds that require a newer address type?" in theory, you should always be able to spend older address types
13417:30 <ls55> much: great chart
13517:30 <antonleviathan> sipa: i'd agree with that, unfortunately negligence on security and privacy is the standard for most
13617:30 <Murch> ls55: Only about 13.6M UTXOs are know to be segwit and some share of the 15.4M P2SH are wrapped segwit. So all in all, definitely less than 50%
13717:30 <sipa> Especially on a system as public as a blockchain, decent privacy really demands that nearly everyone favors the more private solution. If that solution comes at a significant cost, it just won't be used.
13817:30 <Bayer> sipa: Yep that makes sense. It's unfortunate but the reality I suppose. More education could help, get the users informed and they can put pressure on those larger players.
13917:31 <theStack> ls55: no, because you can't mathematically prove that they can't be spent; by looking at them you see that e.g. the hash is text created by a human, i.e. not a real hash from a preimage
14017:31 <sipa> Designing solutions where privacy doesn't come at a (significant) cost is the first hurdle.
14117:31 <Murch> ls55: UTXO set repartitioned by output type: https://txstats.com/dashboard/db/utxo-set-repartition-by-output-type
14217:31 <josibake> antonleviathan, furszy: regarding making it configurable, imo bitcoin core wallet should try to be fairly balanced by default. meaning, reasonable efficiency and reasonable privacy. this leaves room for other wallets to specialize in being a "super efficient wallet" or a "super private wallet"
14317:32 <josibake> sipa: great point!
14417:32 <antonleviathan> i see, that's a sensible approach
14517:32 <antonleviathan> core is for all
14617:33 <ls55> theStack: Got it
14717:33 <sipa> Offering options, or "expert mode" workflows that allow for more configurability is of course nice, but it shouldn't be a replacement for thinking about privacy by default.
14817:33 <Murch> antonleviathan: cost is easy to quantify, privacy/security not as much
14917:33 <vnprc> josibake: ah, i think i had a misconception about how this works, thx
15017:34 <furszy> yeah josibake, problem is where the line between "reasonable privacy" and "there is no privacy" is hehe
15117:34 <sipa> Murch: Yeah...
15217:34 <ls55> Murch: very insteresting chart. The vast majority are still P2PKH.
15317:35 <josibake> sipa: privacy by default is the only way to actually help users be more private. of course, having more options to allow users to opt in to sacrificing efficiency for more privacy is also good
15417:35 <Murch> Aye, and all of those eventually need to go through the eye of the needle that is blockspace supply ;)
15517:35 <josibake> so i saw some good answers regarding coin selection, but can someone briefly explain what the "waste metric" is?
15617:36 <sipa> My somewhat cynical view is that options/expert mode things are really only useful for education and PR. They don't meaningfully contribute to privacy.
15717:38 <josibake> sipa: i tend to agree, altho i think adding things as optional/expert mode can be a great way to see how they perform in the wild, with the goal of iterating on them and making them default
15817:38 <josibake> but if they are always optional, then yes, i dont think they meaningfully contribute
15917:39 <josibake> hint for waste metric: https://github.com/bitcoin/bitcoin/blob/be7a5f2fc400e7a3ef72dedbdcf49dd6c96d4f9e/src/wallet/coinselection.cpp#L374
16017:39 <Bayer> Murch: wow that chart. Very curious to see how that'll look like in 10 years
16117:39 — Murch thinks that he should blog about the waste metric. 🤔
16217:40 <vnprc> waste metric looks like a combination of unnecessary fees and/or the cost of creating a new change output
16317:40 <vnprc> murch: +1
16417:41 <antonleviathan> murch: +1
16517:41 <ls55> murch: +1
16617:41 <josibake> murch: i think everyone really wants a blog on the waste metric..
16717:41 <josibake> vnprc: what do you mean by "unnecessary fees" ?
16817:41 <Bayer> I guess it calculates the waste based on different inputs being used for said tx. I assume we're aiming for less waste
16917:43 <josibake> Bayer: the goal is definitely less waste!
17017:43 <Bayer> lol well I got that right!:)
17117:43 <vnprc> I recall branch and bound seeks to eliminate change outputs by matching input UTXO values to the amount the user wants to spend. I think it does this by setting a threshold and donating the small excess UTXO value in the form of fees. Just going off my memory here.
17217:43 <vnprc> murch: please correct me if i am wrong
17317:43 <josibake> the main idea is we have a long term fee rate estimate and we compare what it would cost to spend now vs spend this tx in the future with the LTFRE
17417:44 <svav> SelectCoinsBnB uses a Branch and Bound algorithm to explore a bounded search tree of potential solutions, scoring them with a metric called “waste.” Notably, the Branch and Bound algorithm looks for an exact solution and never produces a change output. As such, it’s possible for SelectCoinsBnB to fail even though the wallet has sufficient
17517:44 <svav> funds.
17617:45 <Murch> vnprc: That's right
17717:45 <vnprc> yesssss!
17817:46 <antonleviathan> whats "LTFRE"?
17917:46 <sipa> Long Term Fee Rate Estimate
18017:46 <josibake> this next question is a bit more open ended (no wrong answers) and is similar to the discussion we just had about privacy vs efficiency: are there other things/metrics we could consider during coin selection besides just the waste metric?
18117:46 <svav> The above re SelectCoinsBnB was a copy and paste from my notes but it seemed relevant
18217:46 <Murch> Yeah, the `waste metric` compares the cost of the inputs currently selected to a hypothetical cost of spending them later at a longterm feerate estimate. It also adds the cost of creating and spending change, or if there is no change, the excess beyond the target that is dropped to the fees to make the changeless transaciton
18317:47 <Murch> svav: Yep, that's where the waste metric was first introduced, but we've since generalized it to be used as a prioritization tool to pick from multiple input set candidates in transaction building
18417:48 <theStack> how would that LTFRE roughly work? and what is considered "long-term"... weeks, months, years?
18517:49 <Murch> We had a Review Club about the PR that started using waste metric in that manner: https://bitcoincore.reviews/22009
18617:49 <Murch> theStack: We used the time-proven method of Murch's gut feeling and went with a static 10 s/vB
18717:49 <josibake> theStack: great question. right now LTFRE is a "magic number" of 10 s/vb
18817:49 <theStack> i have to think of that famous quote "prediction is very difficult, epsecially if it's about the future" :)
18917:49 <josibake> so its a "magic murch number" :D
19017:49 <theStack> Murch: :D
19117:50 <theStack> murchic number
19217:50 <vnprc> josibake: personally, i would prefer to spend smaller UTXOs over larger ones just to maintain a low profile on-chain
19317:50 <Murch> theStack: It's been a bit high in the past 9 months or so
19417:51 <josibake> vnprc: thats a good example! so this would be an example of a "privacy metric", perhaps preferring many small inputs and no change vs one giant input with a big change output that says "i have a lot of bitcoin!"
19517:51 <svav> A line in spend.h is this .............. /** Other is a catch all for anything that doesn't match the known OutputTypes */
19617:51 <svav>     std::vector<COutput> Other; .............. I don't really know much about this, but could this be some sort of exploit risk in the coding, having an "Other" ????
19717:52 <theStack> vnprc: me too, but also for the reason that smaller UTXOs are more likely to be trapped due to being lower than the "effective dust-limit" in the future (not sure if that term is right, but i'm sometimes wondering if some of my UTXOs are too small to be spent in, let's say 10 years due to permanent exponentially increased fee-rates)
19817:53 <vnprc> theStack: another good reason to spend small UTXOs
19917:53 <Murch> svav: IIRC, other means that we either don't know the script type because it's a PSBT we haven't seen the input script for yet, or it's bare multisig or a new type we don't understand
20017:53 <Murch> josibake: Please correct if I'm mixing this up
20117:53 <josibake> svav: this goes back to the txouttype to outputtype mapping: a majority of utxos will fall into p2pkh, p2sh, or bech32. for more complicated script types, rather than have a specific bucket for each (or rather than just use txouttype for the mapping), we are putting them in an others bucket. if we allow mixing, this is no different behavior wise than using one giant vector of all available outputs. hope that
20217:53 <josibake> clarifies?
20317:54 <josibake> murch: yep! on the nose
20417:54 <Murch> theStack: That's a good point. The interesting effect of using the waste metric as described above is that it prefers bigger input sets at low feerates. It also prefers changeless transactions. So if there is an input set that uses small UTXOs and combines to the right value we'll prefer that (unless there is something that scores even better)
20517:55 <ls55> Are "more complicated script types" non-standard and multisig ?
20617:55 <Murch> The old Bitcoin Core coin selection (misleading called knapsack) also very keenly spends tiny UTXOs for that reason
20717:56 <svav> josibake ok thanks
20817:56 <josibake> ls55: another example could be P2SH with some sort of complicated redeem script
20917:56 <josibake> in the interest of time, im gonna throw out the last two questions
21017:57 <ls55> josibake: Got it
21117:57 <josibake> in the PR, we run over each OutputType in AttemptSelection, but are there other spots in the code we could have added this avoid-mixing logic?
21217:58 <josibake> and lastly, for the c++ fans, what is the erase/remove idiom? why is it the preferred method for erasing elements from a container?
21317:58 <josibake> feel free to throw out answers for whichever one is more interesting to you :D
21417:58 <ls55> `std::remove_if` swaps elements inside the vector in order to put all elements that do not match the predicate towards the beginning of the container.
21517:58 <ls55> `remove_if` then returns an iterator which points to the first element which matches the predicate. In other words, an iterator to the first element to be removed.
21617:58 <ls55> `std::vector::erase` erases the range starting from the returned iterator to the end of the vector, such that all elements that match the predicate are removed.
21717:59 <theStack> Murch: seems like a good idea. reaching changeless transactions (if it's not "send-to-myself") are rather rare i guess though in practice? (but maybe i'm think in too small scale, in wallets with a huge number of UTXOs it's probably pretty likely)
21817:59 <sipa> Since C++20 you don't need the erase/remove idiom anymore, and you can just use `std::erase` ;)
21917:59 <josibake> sipa: TIL!
22017:59 <Murch> theStack: It's a surprisingly decent rate when you get over 100 or so
22117:59 <Murch> Depends on the value diversity of course and what sort of payemnts you make
22218:00 <josibake> ls55: great explanation!
22318:01 <josibake> out of time (but feel free to stay and chat!). thanks everyone for attending! really enjoyed the discussions on this one
22418:01 <josibake> #endmeeting
22518:01 <larryruane> thanks josibake, great discussion!
22618:01 <theStack> thanks for hosting josibake!
22718:01 <Bayer> thanks josiblake!!
22818:01 <ls55> Thanks josibake
22918:01 <svav> Thanks josibake and all
23018:01 <Murch> theStack: I ran some simulations on the bustabit-hot-wallet dataset yesterday, and depending on different LTFRE values, I saw 14-40% changeless transactions
23118:02 <antonleviathan> thanks josibake, this was great, i'll be back! (arnold voice)
23218:02 <josibake> theStack: regarding changeless, it's actually possible to do it on a majority of txs, depending on how diverse your UTXO pool is
23318:02 <theStack> Murch: interesting, so definitel worth a try... i would have expected to reach a "decent rate" by having like thousands or more UTXOs
23418:02 <Murch> This is on a wallet that gets twice as many deposits as withdrawals, though
23518:02 <josibake> antonleviathan: good to hear! for all the first timers, we do this every week :D
23618:02 <larryruane> is bitcoin core compiled with c++20?
23718:02 <vnprc> thanks josibake
23818:02 <Murch> Thanks for the fun!
23918:02 <vnprc> next time more puns pls
24018:02 <Bayer> This was great, will certainly be back:)
24118:02 <ls55> larryruane: I think it is c++17
24218:03 <josibake> larryruane: c++17, iirc?
24318:03 <sipa> c++17
24418:03 <sipa> for now
24518:03 <josibake> sipa: :D
24618:03 <larryruane> thanks