비트코인 멤풀(Mempool) 2탄 - Minimum Fee Rate
2025.12.20 - [분류 전체보기] - 비트코인 멤풀(Mempool)이란?
비트코인 멤풀(Mempool)이란?
비트코인 p2p를 하거나, 거래소에서 개인지갑으로 옮기는 등, 트랜잭션 데이터를 확인할때 우리는 멤풀을 확인한다.멤풀이 자세히 무엇인지 알아보자멤풀이란? 멤풀의 역할멤풀이란, 새로운 트
3min-bitcoin.tistory.com
1탄에선 멤풀이 무엇인가, 멤풀의 기본적인 동작 방식을 알아봤다.
멤풀은 각 노드의 독립적인 설정이므로, 개개인이 어떻게 설정하냐에 따라서 조금씩 다르게 동작할 수 있다.
2탄에선 세부적인 옵션들을 조금 살펴보자.
Minimum Fee Rate

멤풀은 위와 같이 최대 크기가 있다.
이 설정은 노드 설정의 maxmempool 값을 조절하여 키우거나 줄일 수 있다.
기본값은 300MB로 되어있다. (1탄에서 확인)
트랜잭션이 많아져서 maxmempool보다 많아지면, 멤풀은 최저 수수료율(feerate)을 가진 트랜잭션을 추방한다.
추방하는데서 그치는게 아니다. 멤풀은 동적으로 최저 수수료율을 조절한다.
이 경우에는 높혀서 낮은 수수료율을 가진 트랜잭션을 일시적으로 멤풀에 들어오는 것을 차단한다.
멤풀은 동적으로 최저 수수료율을 위 그림의 공식대로 조절한다.
- minrelaytxfee: 사용자 노드 설정으로 조절하는 값이며, 최저 수수료율을 명시할 수 있다. 기본적으로는 1 sats/vB로 되어있다.
- minmempoolfee: 동적으로 조절되는 값이며, 멤풀이 꽉차면 오르고, 텅텅비면 내려간다.
- minrelayfee: 위 두 값 중 최대값으로 설정된다. 멤풀이 텅텅 비어있으면 minrelaytxfee의 기본값을 따라간다.
위 내용이 소스코드에 어떻게 설정되어있는지 하나씩 자세히 살펴보자.
// validation.cpp 702번 라인
// Compare a package's feerate against minimum allowed.
bool CheckFeeRate(size_t package_size, CAmount package_fee, TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_pool.cs)
{
AssertLockHeld(::cs_main);
AssertLockHeld(m_pool.cs);
CAmount mempoolRejectFee = m_pool.GetMinFee().GetFee(package_size);
if (mempoolRejectFee > 0 && package_fee < mempoolRejectFee) {
return state.Invalid(TxValidationResult::TX_RECONSIDERABLE, "mempool min fee not met", strprintf("%d < %d", package_fee, mempoolRejectFee));
}
if (package_fee < m_pool.m_opts.min_relay_feerate.GetFee(package_size)) {
return state.Invalid(TxValidationResult::TX_RECONSIDERABLE, "min relay fee not met",
strprintf("%d < %d", package_fee, m_pool.m_opts.min_relay_feerate.GetFee(package_size)));
}
return true;
}
새로운 트랜잭션이 들어왔을 때 최저 수수료율을 체크하고 이보다 수수료를 적게 제출했으면 거부하는 로직이다.
두 개의 if 조건문이 있는데, 처음은 minmempoolfee값을 보고 동적으로 현재 최저 수수료율을 만족하는지 체크하는 로직이다.
두 번째 if 조건문은, 노드에 설정된 minrelaytxfee값보다 수수료율이 큰지 확인하는 로직이다.
다음은 동적으로 값이 어떻게 조절되는지 로직을 살펴보자.
// validation.cpp 1167라인
// We set the new mempool min fee to the feerate of the removed set, plus the
// "minimum reasonable fee rate" (ie some value under which we consider txn
// to have 0 fee). This way, we don't allow txn to enter mempool with feerate
// equal to txn which were removed with no block in between.
CFeeRate removed(it->GetModFeesWithDescendants(), it->GetSizeWithDescendants());
removed += m_opts.incremental_relay_feerate;
trackPackageRemoved(removed);
maxFeeRateRemoved = std::max(maxFeeRateRemoved, removed);
// validation.cpp 1150 라인
void CTxMemPool::trackPackageRemoved(const CFeeRate& rate) {
AssertLockHeld(cs);
if (rate.GetFeePerK() > rollingMinimumFeeRate) {
rollingMinimumFeeRate = rate.GetFeePerK();
blockSinceLastRollingFeeBump = false;
}
}
위 코드는 멤풀이 꽉차서 추방되는 트랜잭션의 수수료율을 확인하고, 동적으로 minmempoolfee 값을 올리는 로직이다.
trackPackageRemoved 함수를 보면 rollingMinimumFeeRate값이 있는데 이게 동적으로 변경되는 minmempoolfee을 의미한다.
방금 추방된 수수료를 기준으로 커트라인을 올려버리는 지점이다.
// validation.cpp 330번 라인
static const int ROLLING_FEE_HALFLIFE = 60 * 60 * 12; // public only for testing
// validation.cpp 1126번 라인
CFeeRate CTxMemPool::GetMinFee(size_t sizelimit) const {
LOCK(cs);
if (!blockSinceLastRollingFeeBump || rollingMinimumFeeRate == 0)
return CFeeRate(llround(rollingMinimumFeeRate));
int64_t time = GetTime();
if (time > lastRollingFeeUpdate + 10) {
double halflife = ROLLING_FEE_HALFLIFE;
if (DynamicMemoryUsage() < sizelimit / 4)
halflife /= 4;
else if (DynamicMemoryUsage() < sizelimit / 2)
halflife /= 2;
rollingMinimumFeeRate = rollingMinimumFeeRate / pow(2.0, (time - lastRollingFeeUpdate) / halflife);
lastRollingFeeUpdate = time;
if (rollingMinimumFeeRate < (double)m_opts.incremental_relay_feerate.GetFeePerK() / 2) {
rollingMinimumFeeRate = 0;
return CFeeRate(0);
}
}
return std::max(CFeeRate(llround(rollingMinimumFeeRate)), m_opts.incremental_relay_feerate);
}
멤풀이 비어있다면 minmempoolfee 동적 최저 수수료율은 위 로직에 따라 다시 줄어든다.
위 코드는 해당 수수료율 하락을 관리하는 코드다. 로직을 간단히 살펴보자.
멤풀은 멤풀이 얼마나 비어있느냐에 따라 수수료율이 내려가는 속도(반감기, halflife)가 조절된다. 기본값은 12시간이다.
if (DynamicMemoryUsage() < sizelimit / 4) 이 부분을 보면, 최대 사이즈의 25% 정도 이하로 사용중이면 줄어드는 속도를 4배로 설정한다. 50% 이하로 사용중이면 2배로 설정하는것을 볼 수 있다.
실제 감소 로직은 이 부분으로, 분모를 보면 밑을 2로 하는 지수로 설계됨을 알 수 있다.
rollingMinimumFeeRate = rollingMinimumFeeRate / pow(2.0, (time - lastRollingFeeUpdate) / halflife);
그리고 지수값은 (현재시간 - 마지막으로 수수료율이 변경된 시간) / 내려가는 속도로 설정된다.
멤풀이 많이 비어있으면 halflife값을 더욱 낮게 해서 분모를 크게 만듦으로, rollingMinimumFeeRate 값을 작게 만드는 것을 확인할 수 있다.
결론
내가 운영하는 나의 노드를 커스터마이징 하기 위한 minimum fee rate에 대해서 알아봤다.
실제로 커스터마이징 할 수 있는것은, minrelaytxfee이고, 0.1 sats/vB같은 설정에 동의하고 하고 싶다면 직접 노드 설정을 변경하면 된다. 그리고 멤풀에 더 많은 트랜잭션을 보관하고 싶다면 최대 용량을 늘려도 된다.
다음에는 멤풀이 노드 안에서 어떻게 구성되는지 알아보려 한다.
참고문헌
https://bitcoin.stackexchange.com/questions/58083/is-it-possible-to-set-a-dynamic-minrelaytxfee
Is it possible to set a dynamic -minrelaytxfee?
When I start bitcoind, I like to set a -minrelaytxfee to save bandwidth on transactions that will likely never make it in to the blockchain (because their fees are too low). However, when the netw...
bitcoin.stackexchange.com
https://learnmeabitcoin.com/technical/mining/memory-pool/
Memory Pool | Waiting Area for Transactions
Memory Pool Waiting area for transactions Current Mempool Size: 35,734 transactions Note: This is the size of the mempool for my local node. The size of your memory pool will differ depending on how long your node has been online and which nodes you are co
learnmeabitcoin.com