Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Broadcast to a single peer #5

Merged
merged 2 commits into from
Jun 3, 2024
Merged

Conversation

alfred-hodler
Copy link
Owner

The problem with blasting transactions to multiple peers is that it increases the chance of landing on a surveillance node. This isn't a problem when using Tor; it is mainly a problem for clearnet connections.

This change ensures that we broadcast to just one peer and then wait for a roundtrip confirmation in the form of an inventory message from other connected peers.

This is marginally slower than the blasting strategy but it increases both privacy for clearnet connections and gives us certainty in the form of verifying that our transaction has propagated through the network.

The selected broadcast peer is given 10 seconds for our transaction(s) to appear on the network, after which we rotate the peer.

Other changes:

  • bump version to 0.4.0 for both lib and bin
  • redo the Report struct to better reflect completion outcomes
  • deprecate the --tesnet switch in bin in favor of --network
  • remove the send_unsolicited option (unnecessary)

The problem with blasting transactions to multiple peers is that it
increases the chance of landing on a surveillance node. This isn't a
problem when using Tor; it is mainly a problem for clearnet connections.

This change ensures that we broadcast to just one peer and then wait for
a roundtrip confirmation in the form of an `inventory` message from
other connected peers.

This is marginally slower than the blasting strategy but it increases
both privacy for clearnet connections and gives us certainty in the form
of verifying that our transaction has propagated through the network.

The selected broadcast peer is given 10 seconds for our transaction(s)
to appear on the network, after which we rotate the peer.

Other changes:

- bump version to 0.4.0 for both lib and bin
- redo the `Report` struct to better reflect completion outcomes
- deprecate the `--tesnet` switch in bin in favor of `--network`
- remove the `send_unsolicited` option (unnecessary)
@starius
Copy link

starius commented May 31, 2024

Hi @alfred-hodler !
I'm not very familiar with Rust. How can I try this PR locally?
Is broadcasting to only one peer the only strategy now? (If yes, it is a good thing, IMHO.)
Also, does the tool wait for random time for each peer before disconnecting it in the end? (To avoid time correlation.)

@alfred-hodler
Copy link
Owner Author

How can I try this PR locally?

git clone https://github.com/alfred-hodler/pushtx
git checkout single-peer-broadcast
cargo run -- --network testnet # (or mainnet or signet)

Is broadcasting to only one peer the only strategy now?

Yes.

Also, does the tool wait for random time for each peer before disconnecting it in the end? (To avoid time correlation.)

It doesn't wait because there are many other heuristics that we could be identified by. We are sending a very identifiable version message, so something would have to be done on that front too, which is very hard. If that level of surveillance is a concern, such as targeted heuristics, we just need to use Tor -- at that point these heuristics and fingerprints don't really matter anymore.

@vasild
Copy link
Contributor

vasild commented Jun 1, 2024

Does this broadcast always to a single peer, regardless of whether Tor is available or not? I think a good strategy would be:

  • If Tor is available, then send to a few .onion peers or even IPv4 or IPv6 peers via the Tor proxy at the same time. Wait, if not received back, repeat.
  • If Tor is not available, then send to a single IPv4 or IPv6 peer directly via clearnet. Wait, if not received back, repeat.

@starius
Copy link

starius commented Jun 2, 2024

@vasild

  • If Tor is available, then send to a few .onion peers

that is suboptimal, since ISP can see a spike of Tor traffic on the sender end and correlate it with the appearance of a transaction in mempool.

If do this, at least add a flag please. I would like to have Wasabi grade privacy and they send to one peer even though Tor (and onion) is used. And it works quite fast in Wasabi, because most peers do their job of broadcasting transaction further.

@starius
Copy link

starius commented Jun 2, 2024

I'm trying to build it on Debian Sid. Earlier Debian versions have too old rustc and it refused to build.

Still failing to build it.

$ rustc --version
rustc 1.74.1

pushtx$ cargo run -- --help
   Compiling typenum v1.17.0
   Compiling version_check v0.9.4
   Compiling libc v0.2.155
   Compiling proc-macro2 v1.0.83
   Compiling generic-array v0.14.7
   Compiling unicode-ident v1.0.12
   Compiling cc v1.0.98
   Compiling bitcoin-internals v0.2.0
   Compiling quote v1.0.36
   Compiling secp256k1-sys v0.9.2
   Compiling crossbeam-utils v0.8.20
   Compiling log v0.4.21
   Compiling autocfg v1.3.0
   Compiling slab v0.4.9
   Compiling syn v2.0.65
   Compiling hex_lit v0.1.1
   Compiling hex-conservative v0.1.2
   Compiling utf8parse v0.2.1
   Compiling anstyle-parse v0.2.4
   Compiling bitcoin_hashes v0.13.0
   Compiling crypto-common v0.1.6
   Compiling block-buffer v0.10.4
   Compiling colorchoice v1.0.1
   Compiling anstyle-query v1.0.3
   Compiling anstyle v1.0.7
   Compiling is_terminal_polyfill v1.70.0
   Compiling bitcoin v0.31.2
   Compiling byteorder v1.5.0
   Compiling socks v0.3.4
   Compiling anstream v0.6.14
   Compiling crossbeam-channel v0.5.13
   Compiling digest v0.10.7
   Compiling secp256k1 v0.28.2
   Compiling mio v0.8.11
   Compiling socket2 v0.5.7
   Compiling bech32 v0.10.0-beta
   Compiling thiserror v1.0.61
   Compiling strsim v0.11.1
   Compiling nohash-hasher v0.2.0
   Compiling heck v0.5.0
   Compiling anyhow v1.0.86
   Compiling clap_lex v0.7.0
   Compiling cfg-if v1.0.0
   Compiling keccak v0.1.5
   Compiling dns-lookup v2.0.4
   Compiling sha3 v0.10.8
   Compiling clap_builder v4.5.2
   Compiling clap_derive v4.5.4
   Compiling peerlink v0.8.0
   Compiling thiserror-impl v1.0.61
   Compiling env_filter v0.1.0
   Compiling data-encoding v2.6.0
   Compiling fastrand v2.1.0
   Compiling hex v0.4.3
   Compiling pushtx v0.4.0 (/home/user/pushtx/pushtx)
error[E0562]: `impl Trait` only allowed in function and inherent method return types, not in `impl` method return types
   --> pushtx/src/p2p/client.rs:126:56
    |
126 |     fn receiver(&self) -> &crossbeam_channel::Receiver<impl Into<super::Event<PeerId>>> {
    |                                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: see issue #91611 <https://github.com/rust-lang/rust/issues/91611> for more information

error[E0562]: `impl Trait` only allowed in function and inherent method return types, not in trait method return types
  --> pushtx/src/p2p.rs:39:56
   |
39 |     fn receiver(&self) -> &crossbeam_channel::Receiver<impl Into<Event<P>>>;
   |                                                        ^^^^^^^^^^^^^^^^^^^
   |
   = note: see issue #91611 <https://github.com/rust-lang/rust/issues/91611> for more information

For more information about this error, try `rustc --explain E0562`.
error: could not compile `pushtx` (lib) due to 2 previous errors
warning: build failed, waiting for other jobs to finish...

@alfred-hodler
Copy link
Owner Author

@vasild I agree with @starius in that it's not really necessary to broadcast to multiple peers. Transaction propagation seems to be very fast. Connecting to at least one onion peer would be nice if we have Tor, but to get there some refactoring is needed since the primary way of getting peers is currently through DNS seeds and those don't return onion domains. This is something that we can put on the roadmap for the future.

@starius It seems that some generic constructs weren't compatible with your version of Rust. I've just issued an update, please pull and try again.

@starius
Copy link

starius commented Jun 3, 2024

@alfred-hodler It builds now.

It works! I tested it a couple of times in signet. Thank you very much for the tool! I'll definitely add it to my favorite tool collection and use instead of Wasabi to broadcast transactions!

One thing which is confusing is that it expects me to type Ctrl+D after entering the transaction. I pressed just Enter instead and thought that it is not working. Can you catch Enter as well, not only Ctrl+D, please? Or at least print in the prompt that the user has to press Ctrl+D after typing the transaction.

Another thing. I know that terminals don't work very well with long inputs (longer than 4096 characters). I ran into this issue in incli tool when entered a large transaction and it only caught first 4096 characters. I don't have such a transaction now, so I can't test. Can you test that it is entered correctly? Also, if you catch a parsing error for the transaction and the length of the input is around 4096 characters, you can print a warning to the user explaining that the transaction is too long to enter in the stdin and that they should save it to a file and profile file name in -f option.

Also, I see that option --tor-mode has the following options:

          - try:  Use Tor if available. If not available, connect through clearnet
          - no:   Do not use Tor even if available and running
          - must: Exclusively use Tor. If not available, do not broadcast

What if the user is forwarding all network traffic to Tor using operating systems capabilities (e.g. running in QubesOS behind Whonix Gateway). Then port 9050 may not be available locally, but just normal network traffic is sent through Tor and *.onion sites work in any browser (are resolved to local IP space and handled automatically by the gateway). Will the tool use onion peers in this case or will it thing that Tor is not available, because port 9050 is not available? Don't we need another option for this mode (e.g. "gateway")?

We are sending a very identifiable version message, so something would have to be done on that front too, which is very hard.

Can you also deal with this, please? E.g. send some popular user agent.

@alfred-hodler alfred-hodler merged commit 008133e into master Jun 3, 2024
3 checks passed
@alfred-hodler
Copy link
Owner Author

One thing which is confusing is that it expects me to type Ctrl+D after entering the transaction. I pressed just Enter instead and thought that it is not working. Can you catch Enter as well, not only Ctrl+D, please? Or at least print in the prompt that the user has to press Ctrl+D after typing the transaction.

Ctrl-D (or Ctrl-Z on Windows) means "end of stream" and that's a pretty standard way to terminate stdin. Tools that read their input through stdin always take this approach and don't terminate on Enter. cat, wc and other standard Linux tools are good examples of this, as well as gpg, which also just takes input indefinitely until you signal the end of stream.

More interactive programs might indeed handle different types of input, but this tool is meant to follow a Unix philosophy. In the end, if we terminated with Enter, we wouldn't be able to enter multiple transactions, which is possible right now (whether this is a good idea is a different discussion).

Another thing. I know that terminals don't work very well with long inputs (longer than 4096 characters). I ran into this issue in incli tool when entered a large transaction and it only caught first 4096 characters. I don't have such a transaction now, so I can't test. Can you test that it is entered correctly? Also, if you catch a parsing error for the transaction and the length of the input is around 4096 characters, you can print a warning to the user explaining that the transaction is too long to enter in the stdin and that they should save it to a file and profile file name in -f option.

The 4096 char limit seems to be real. It's something I can deal with in the next version.

What if the user is forwarding all network traffic to Tor using operating systems capabilities (e.g. running in QubesOS behind Whonix Gateway). Then port 9050 may not be available locally, but just normal network traffic is sent through Tor and *.onion sites work in any browser (are resolved to local IP space and handled automatically by the gateway). Will the tool use onion peers in this case or will it thing that Tor is not available, because port 9050 is not available? Don't we need another option for this mode (e.g. "gateway")?

The tool wouldn't use onion peers either way because we are getting them through DNS seeds, which return A type records -- meaning that domain names (which includes .onion) aren't included there. We have some hardcoded onion peers but that's just in our hardcoded lists -- which are used only for fallback in case the DNS method doesn't work.

Some redesign/refactor there might be needed to get this to work. I'd be interested in getting some help in the form of a high quality PR.

Can you also deal with this, please? E.g. send some popular user agent.

If you're using the library, you can override the user agent right now. The binary uses the default one. The problem with the whole user agent business is that the version message contains a lot more than that, such as our block height and local time. Unless you set those too, it's obvious you're an "unusual" peer. I generally don't think anyone should ever be broadcasting transactions through clearnet, but if they are indeed doing that, fiddling with the user agent won't really help them hide better.

@alfred-hodler alfred-hodler deleted the single-peer-broadcast branch June 3, 2024 03:14
@starius
Copy link

starius commented Jun 3, 2024

Few more ideas :)

Ctrl-D (or Ctrl-Z on Windows) means "end of stream" and that's a pretty standard way to terminate stdin.

Can you mention Ctrl+D / Ctrl+Z on Windows in the prompt message, please? I was waiting for few minutes after pressing Enter, thinking that the tool is stick, until I remembered Ctrl+D thing :)

if we terminated with Enter, we wouldn't be able to enter multiple transactions, which is possible right now

After reading and parsing the first transaction the tool could make next prompt like this:

Successfully parsed 1 transaction, transmitting 0.01 BTC in mainnet, having 1 input and 2 outputs.
Outputs:
1xxxx: 0.005 BTC
3xxxx: 0.005 BTC
Would you like to (b)roadcast the transaction, (r)ead more transactions or (q)uit? (b/r/q)

This will make the tool more interactive, not like wc or cat, but will safeguard the user against accidentally sending wrong transaction.

If can be disabled with --force or something.

Also, cat mode can be enabled explicitly by passing - as input file name (to run automatically, from scripts).

We have some hardcoded onion peers but that's just in our hardcoded lists -- which are used only for fallback in case the DNS method doesn't work.

Bitcoin Core has a large list of hardcoded onion (and other types of) peers. Does it make sense to ember the whole list into the tool?

https://github.com/bitcoin/bitcoin/blob/master/contrib/seeds/nodes_main.txt

I'd be interested in getting some help in the form of a high quality PR.

If only I had Rust skills to do it. I'll ask my friends who write in Rust to take a look :)

Unless you set those too, it's obvious you're an "unusual" peer.

Some idea. Let's connect to the first peer with some default settings (e.g. default user agent, block height = 0 etc), memorize what it responded and connect to the second peer using data returned by the first peer. Then connect to the third peer using the data returned by the second peer etc.

@alfred-hodler
Copy link
Owner Author

Can you mention Ctrl+D / Ctrl+Z on Windows in the prompt message, please? I was waiting for few minutes after pressing Enter, thinking that the tool is stick, until I remembered Ctrl+D thing :)

Here you go: 90ad6b9

After reading and parsing the first transaction the tool could make next prompt like this:

Successfully parsed 1 transaction, transmitting 0.01 BTC in mainnet, having 1 input and 2 outputs.
Outputs:
1xxxx: 0.005 BTC
3xxxx: 0.005 BTC
Would you like to (b)roadcast the transaction, (r)ead more transactions or (q)uit? (b/r/q)

Ok, that's an interesting take on this tool. Initially I envisioned it a simple BYOW (Bring Your Own Wallet) solution where you do all the validation elsewhere and just pipe the output into this. Your suggestion would make this tool more of a full fledged "swiss army knife" in some sense and that would come with its own complexity.

Bitcoin Core has a large list of hardcoded onion (and other types of) peers. Does it make sense to ember the whole list into the tool?

https://github.com/bitcoin/bitcoin/blob/master/contrib/seeds/nodes_main.txt

We already have those, but we're only using them for fallback. The problem with using them as a primary source is that these would be very outdated compared with what DNS seeds are returning and only to be used as last resort when you're out of other options.

Some idea. Let's connect to the first peer with some default settings (e.g. default user agent, block height = 0 etc), memorize what it responded and connect to the second peer using data returned by the first peer. Then connect to the third peer using the data returned by the second peer etc.

That would take too long, since handshakes take a while to complete. Right now we connect to a bunch of peers in parallel and this proposal would make that process serial/sequential. The whole process would take longer than now, and I'm not sure that it's worth it.

We could just expose a -u / --user-agent option and let the user pass whatever they want there.

Either way, we should probably move this type of discussion elsewhere.

@vasild
Copy link
Contributor

vasild commented Jun 10, 2024

Let's connect to the first peer with some default settings (e.g. default user agent, block height = 0 etc), memorize what it responded and connect to the second peer using data returned by the first peer

The pattern of connect-send-tx-disconnect is unique enough to distinguish that this is the first broadcast of the transaction, most likely by its owner. The individual fields in the version message cannot obfuscate this, but they can undesirably reveal further information if they are not generic enough (e.g. clock, odd height). This may allow linking repeated broadcasts to an otherwise unrelated transactions.

We could just expose a -u / --user-agent option ...

Given the above that would bring zero-value, but it would make it possible for the user to shoot themselves in the foot by specifying an unique user agent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants