-
Notifications
You must be signed in to change notification settings - Fork 127
server/asset/{btc,doge,ltc,bch}: median-fee based fee rate estimation backup #1597
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
Conversation
buck54321
commented
Apr 22, 2022
Very cool for a fallback if Although honestly if I'm picking a mainnet BTC fee rate, I barely even look at the recent past blocks, instead looking at the state of mempool. Say the next block is shaping up like the following: Even if that mined block 733097 was full or nearly full, I'd make my decision based primarily on the next block's worth of mempool on the left:
I'd probably pick something around 7 or 8 in the above case case. Regardless, in this second case, I would barely consider the last mined block because the fees in mempool could be going up rapidly, and if I picked based on the last block, I could be immediately several blocks deep in mempool. Do you think there's a way to bring mempool into the calculus you've got so far? |
I'm happy to work some mempool in, but the whole point here is that mempool is not developed enough for |
Ah, for e.g. with doge mempool is probably fairly empty. |
Another example from electrum x server, which can target a depth in the memory pool, which is conceptually what I do mentally At the end of the day, if mempool is fairly empty, one could even go with the min relay fee, possibly plus some extra if blocks have historically been fairly full (where your current code would be valuable, although potentially tricked to overpaying). If mempool is of significant size, I'd only look at mempool, almost completely disregarding past blocks. |
As per offline discussion, using mempool is a little fraught. I'm admittedly still trying to wrap my head around I do think that we could get something out of a static analysis of mempool txs, but this raises lots of questions about the right way to interpolate. I also question whether we should trust a mempool that the node clearly doesn't, but I don't know the details of how mempool is populated on startup. Anyway, this block-based method is meant as a backup that should just kinda work for a rough estimate on any chain. |
Sounds reasonable. It's a sound backup. For ref, here's a link to my early experimentation with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tACK.
Just needs a rebase to deal with conflicts from when I added the block deserializaer.
We can go with your bool name, but I'd prefer my anylist
approach, but whatever really.
Rebase branch I'm evaluating: chappjc@abb0db6
server/asset/btc/rpcclient.go
Outdated
prevs = make(map[int]int64, 1) | ||
prevOuts[prevOut.Hash] = prevs | ||
} | ||
prevs[int(prevOut.Index)] = 0 // placeholder |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// value will be retrieved subsequently, after all prevouts for this funding txhash are recorded
server/asset/btc/rpcclient.go
Outdated
if sz == 0 { | ||
return 0, fmt.Errorf("size 0 tx %s", tx.TxHash()) | ||
} | ||
rates = append(rates, uint64(fees)/uint64(sz)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Under non-error conditions you will always append to length numTxs
, so I'd be tempted to do for i, tx := range txs
and rates[i] = ...
to spare modifying len in the slice header on each iteration, but nbd.
@@ -127,9 +130,19 @@ type Backend struct { | |||
chainParams *chaincfg.Params | |||
// A logger will be provided by the dex for this backend. All logging should | |||
// use the provided logger. | |||
log dex.Logger | |||
decodeAddr dexbtc.AddressDecoder | |||
estimateFee func(*RPCClient) (uint64, error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❤️
I'm looking to make a similar change in the future to client where we have the awkward issue of a client/asset.Wallet
having to be a RawRequester
, something that stands out given it's RPC-oriented.
server/asset/btc/btc.go
Outdated
@@ -506,7 +555,7 @@ func (btc *Backend) InitTxSizeBase() uint32 { | |||
|
|||
// FeeRate returns the current optimal fee rate in sat / byte. | |||
func (btc *Backend) FeeRate(_ context.Context) (uint64, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once we get around to updating (*RPCClient).call
to accept a context we should pass it through estimateFee
, esp. if we hit the "hard way" path. In fact, a ctx could still be quite helpful in those loops juts by doing if ctx.Err() != nil { return ctx.Err() }
in case it's shutdown or canceled so it doesn't keep trying getrawtransaction
s e.g.
if err == nil && satsPerB > 0 { | ||
return satsPerB, nil | ||
} else if err != nil { | ||
btc.log.Tracef("Estimatefee error for %s: %v", btc.name, err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just noticing that this could show <nil>
for err
if satsPerB
were just zero. Could be confusing, but meh, it's a TRC.
feeCache struct { | ||
sync.Mutex | ||
fee uint64 | ||
hash chainhash.Hash |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice pattern.
server/asset/btc/btc.go
Outdated
satsPerB, err = btc.node.medianFeeRate() | ||
} | ||
if err != nil { | ||
if errors.Is(err, ErrNoCompetition) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unexport ErrNoCompetition
?
server/asset/btc/btc.go
Outdated
// MaxFeeBlocks is the maximum number of blocks that can be evaluated for | ||
// median fee calculations. If > 100 txs are not seen in the last | ||
// MaxFeeBlocks, an ErrNoCompetition is returned. Default value is 5. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, but the ErrNoCompetition
seems to be internal, not really something of concern for consumer of BackendCloneConfig
Oh, you could also update the hard way to use the msgblock, but it's fine as is.
|
Add methods for calculating the median fee of the most recent block(s). Ensure that there is sufficient data by requiring M transactions in the last N blocks. If not enough data, fallback to a "no-competition" fee rate. Implement a cache to prevent repeated scans between blocks. Some chains provide the getblockstats RPC. Others require manual scanning.