Skip to content

Feat: automatic rbf#313

Merged
gregdhill merged 11 commits intointerlay:masterfrom
sander2:feat/automatic-rbf
Aug 17, 2022
Merged

Feat: automatic rbf#313
gregdhill merged 11 commits intointerlay:masterfrom
sander2:feat/automatic-rbf

Conversation

@sander2
Copy link
Member

@sander2 sander2 commented Jul 22, 2022

I would like to have automated tests but since this really depends on the bitcoin core specifics maybe the best place would be in the lib..

@sander2 sander2 force-pushed the feat/automatic-rbf branch 2 times, most recently from e2b7079 to 4221bbc Compare July 26, 2022 17:03
Either::Left((result, _)) => result?,
Either::Right((Some(Ok((old_fee, new_fee))), _)) => {
tracing::info!("Bumping fee rate from {old_fee} to {new_fee}...");
txid = self.bump_fee(btc_rpc, txid, new_fee).await?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we always want to bump the fee as soon as the estimate is higher that the current fee? If it's inexpensive or free I don't see why not but just want to confirm.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Bumping fees does not cost the client anything, but there is a minimum increase for the network to accept the new tx. I believe that is set to 1 sat/vbyte, and since our price estimate has a granularity of 1 sat/vbyte, we could always bump. But in case 1 of these assumptions fails to hold (in the future) I will add an additional check for it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to just always try and catch the error

@gregdhill
Copy link
Member

I would like to have automated tests but since this really depends on the bitcoin core specifics maybe the best place would be in the lib..

As much as I would like to shift the burden of responsibility I do think we're best placed to test this, besides we already have some integration tests here.

@sander2
Copy link
Member Author

sander2 commented Jul 29, 2022

I would like to have automated tests but since this really depends on the bitcoin core specifics maybe the best place would be in the lib..

As much as I would like to shift the burden of responsibility I do think we're best placed to test this, besides we already have some integration tests here.

Hmm I didn't remember having those tests. It looks like they aren't run in CI though, and the infrastructure is not setup currently to run them. Do you know how we can get a bitcoin regtest node to run in the github test environment?

I do agree that relying on the lib for these tests is not ideal, but we made a decision a long time ago to use the bitcoind simulator instead of using an actual bitcoin node. Do you recall the reason for that? It was a considerable effort to write the simulator so I feel like there must have been a good reason but honestly I can't recall it right now

@gregdhill
Copy link
Member

It looks like they aren't run in CI though, and the infrastructure is not setup currently to run them. Do you know how we can get a bitcoin regtest node to run in the github test environment?

Perhaps this was an oversight in the migration from GitLab to GitHub (although it may have actually been Jenkins). @ns212 would be best placed to answer your question though.

I do agree that relying on the lib for these tests is not ideal, but we made a decision a long time ago to use the bitcoind simulator instead of using an actual bitcoin node. Do you recall the reason for that? It was a considerable effort to write the simulator so I feel like there must have been a good reason but honestly I can't recall it right now

I can't recall the exact reason no, but I think the simulator has its advantages - particularly for unit tests. FWIW I'm not going to block this PR on you adding integration tests but we should definitely test this here and even start to pull other complexities out of the lib.

@sander2 sander2 force-pushed the feat/automatic-rbf branch from 4221bbc to e656cfd Compare August 11, 2022 14:27
@sander2 sander2 force-pushed the feat/automatic-rbf branch from e656cfd to 221939d Compare August 11, 2022 14:54
@sander2 sander2 force-pushed the feat/automatic-rbf branch from 221939d to f4cc2d0 Compare August 12, 2022 06:56
Copy link
Member

@gregdhill gregdhill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bumping implementation does seem overly complex which makes it hard to follow exactly, but I get the intent. I've left a few comments pertaining to the "always increase" approach which I'm hoping you can provide some more insight on.

.try_filter_map(|x| async move {
match btc_rpc.fee_rate(txid) {
Ok(current_fee) => {
if x >= current_fee {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if we should make this configurable, at least if the fee rate increases significantly enough the Vault might prefer to eat the penalty for non-inclusion right?

Copy link
Member Author

@sander2 sander2 Aug 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the Vault might prefer to eat the penalty for non-inclusion right?

I don't understand what you mean here, can you rephrase/elaborate? Are you imagining a scenario where the fees are greater than the sent amount? Keep in mind that when we first creat the tx, we also use the fees that are determined by the oracle's estimate, so I don't think it's bad to always try to use the up-to-date fee rate

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay so this might be an absurd example, but my point is that we may not want to always blindly increase the fee.

So in reference to the fee budget taken from the oracle when the redeem request is made, let's assume we have the following:

  • sat/byte is 100
  • estimated redeem size is 384 bytes
  • fee budget is 38400 sat or 0.000384 BTC = ~$10
  • redeem value is 0.1 BTC (total is 0.100384 BTC)

There is no technical limit to the fee rate so let us assume this increases to 3000 sat/byte which the Vault has to cover out-of-pocket:

  • vault now pays 1152000 sat (0.01152 BTC = ~$275) - maybe less depending on actual size
  • total sent is now 0.11152 BTC

Now assume the Vault decided not to bump the transaction fees:

  • slashed 100% value in collateral (e.g. DOT)
  • punishment fee (10%) of 0.1 BTC also taken = 0.01 BTC (this may also be increased in the future)
  • total lost is 0.11 BTC - lower net loss than bumping the transaction fee

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @sander2 has just reminded me, once the transaction has been sent the Vault anyway risks losing funds if it doesn't bump to include before expiry.

txid = new_txid;
continue 'outer;
}
Err(Error::BitcoinError(x)) if x.rejected_by_network_rules() => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this catch the case that the Vault can not afford the fee increase?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm that's a good one, probably not. We should add an explicit catch for that I guess

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to not abort at all when getting errors. We'll just wait for the original tx

}
Either::Right((Some(Ok((old_fee, new_fee))), continuation)) => {
tracing::debug!("Attempting to bump fee rate from {old_fee} to {new_fee}...");
match self.bump_fee(btc_rpc, txid, new_fee).await {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to end up in a sort of "deadlock" if the Vault is always increasing fees, say if we do receive numerous smaller fee estimate bumps on the parachain? (assuming re-submission pushes the new tx to the "back" of the mempool)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how that'd be possible, no. If bump_fee succeeds, the new tx will end up more to the front of the queue because the miners sort by fee. If it fails there'd be no change

let fee_rate = self.get_fee_rate(parachain_rpc).await?;

tracing::debug!("Using fee_rate = {fee_rate}");
tracing::debug!("Using fee_rate = {} sat/vByte", fee_rate.0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: could implement Display for SatPerVbyte

.checked_div(FixedU128::accuracy())
.ok_or(Error::ArithmeticUnderflow)
.and_then(|x| x.try_into().map_err(Into::<Error>::into));
.and_then(|x| x.try_into().map(|x| SatPerVbyte(x)).map_err(Into::<Error>::into));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.and_then(|x| x.try_into().map(|x| SatPerVbyte(x)).map_err(Into::<Error>::into));
.and_then(|x| x.try_into().map(SatPerVbyte).map_err(Into::<Error>::into));

Copy link
Member

@gregdhill gregdhill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

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.

2 participants