Skip to content

Commit

Permalink
state machine: CF tx size helper, use int for amounts
Browse files Browse the repository at this point in the history
Signed-off-by: Antoine Poinsot <darosior@protonmail.com>
  • Loading branch information
darosior committed Sep 7, 2021
1 parent 2d82d3b commit 2376f1f
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 32 deletions.
2 changes: 1 addition & 1 deletion Model/main.py
Expand Up @@ -84,4 +84,4 @@
with open(f"{REPORT_FILENAME}.txt", "w+", encoding="utf-8") as f:
f.write(report)

# sim.plot_fee_history(start_block,end_block, PLOT_FILENAME, True)
# sim.plot_fee_history(start_block,end_block, PLOT_FILENAME, True)
21 changes: 11 additions & 10 deletions Model/simulation.py
Expand Up @@ -4,7 +4,7 @@
from matplotlib import pyplot as plt
import numpy as np
from pandas import DataFrame
from utils import TX_OVERHEAD_SIZE, P2WPKH_INPUT_SIZE, P2WPKH_OUTPUT_SIZE
from utils import cf_tx_size, P2WPKH_INPUT_SIZE, P2WPKH_OUTPUT_SIZE
from statemachine import StateMachine


Expand Down Expand Up @@ -145,13 +145,11 @@ def amount_needed(self, block_height, expected_new_vaults):
# FIXME: way too much
expected_num_inputs = len(self.wt.fbcoins) + 1
expected_cf_fee = (
TX_OVERHEAD_SIZE
+ expected_num_outputs * P2WPKH_OUTPUT_SIZE
+ expected_num_inputs * P2WPKH_INPUT_SIZE
) * feerate
cf_tx_size(expected_num_inputs, expected_num_outputs) * feerate
)

R += expected_cf_fee
return R
return int(R)

def initialize_sequence(self, block_height):
logging.debug(f"Initialize sequence at block {block_height}")
Expand Down Expand Up @@ -204,7 +202,7 @@ def initialize_sequence(self, block_height):

# Allocation transitions
for i in range(0, self.expected_active_vaults):
amount = 10e10 # 100 BTC
amount = int(10e10) # 100 BTC
self.wt.allocate(self.new_vault_id(), amount, block_height)
self.vault_count += 1

Expand Down Expand Up @@ -295,7 +293,7 @@ def _spend_init(self, block_height):
# If WT fails to acknowledge new delegation, raise AllocationError
try:
# Delegate a vault
amount = 10e10 # 100 BTC
amount = int(10e10) # 100 BTC
vault_id = self.new_vault_id()
logging.debug(
f" Allocation transition at block {block_height} to vault {vault_id}"
Expand Down Expand Up @@ -332,6 +330,7 @@ def top_up_sequence(self, block_height):
logging.debug(
f" Allocation transition at block {block_height} to vault {vault['id']}"
)
assert isinstance(vault["amount"], int)
self.wt.allocate(vault["id"], vault["amount"], block_height)
except (RuntimeError):
logging.debug(f" Allocation transition FAILED for vault {vault['id']}")
Expand Down Expand Up @@ -519,7 +518,9 @@ def plot(self, output=None, show=False):
self.with_fb_coins_dist,
]
)
figure, axes = plt.subplots(subplots_len, 1, sharex=True, figsize=(5.4, subplots_len * 3.9))
figure, axes = plt.subplots(
subplots_len, 1, sharex=True, figsize=(5.4, subplots_len * 3.9)
)
plot_num = 0

# Plot WT balance vs total required reserve
Expand Down Expand Up @@ -865,7 +866,7 @@ def plot_fee_history(self, start_block, end_block, output=None, show=False):
plt.title("Feerate History")
plt.xlabel("Block", labelpad=15)
plt.ylabel("Feerate (sats/vByte)", labelpad=15)
fig.size = 3,7
fig.size = 3, 7

if output is not None:
plt.savefig(f"{output}.png")
Expand Down
45 changes: 24 additions & 21 deletions Model/statemachine.py
Expand Up @@ -14,9 +14,9 @@

from pandas import read_csv
from utils import (
TX_OVERHEAD_SIZE,
P2WPKH_INPUT_SIZE,
P2WPKH_OUTPUT_SIZE,
cf_tx_size,
MAX_TX_SIZE,
CANCEL_TX_WEIGHT,
)
Expand Down Expand Up @@ -165,7 +165,7 @@ def _feerate_to_fee(self, feerate, tx_type, n_fb_inputs):
# feerate is in satoshis/vbyte
cancel_tx_size_no_fb = (CANCEL_TX_WEIGHT[self.n_stk][self.n_man] + 3) // 4
cancel_tx_size = cancel_tx_size_no_fb + n_fb_inputs * P2WPKH_INPUT_SIZE
return cancel_tx_size * feerate
return int(cancel_tx_size * feerate)

def fee_reserve_per_vault(self, block_height):
return self._feerate_to_fee(
Expand All @@ -182,7 +182,7 @@ def Vm(self, block_height):
raise ValueError(
f"Vm = {Vm} for block {block_height}. Shouldn't be non-positive."
)
return Vm
return int(Vm)

def Vb(self, block_height):
"""Amount for a backup feebump coin"""
Expand All @@ -192,7 +192,7 @@ def Vb(self, block_height):
t2 = reserve_rate * (float(P2WPKH_INPUT_SIZE) / 4) + self._feerate_to_fee(
10, "cancel", 0
)
return max(t1, t2)
return int(max(t1, t2))

def fb_coins_dist(self, block_height):
"""The coin distribution to create with a CF TX.
Expand All @@ -211,7 +211,7 @@ def fb_coins_dist(self, block_height):
# Strategy 1
# O = [Vm, MVm, 2MVm, 3MVm, ...]
if self.O_version == 1:
frpv = int(self.fee_reserve_per_vault(block_height))
frpv = self.fee_reserve_per_vault(block_height)
Vm = self.Vm(block_height)
M = self.O_1_factor # Factor increase per coin
O = [Vm]
Expand All @@ -223,6 +223,7 @@ def fb_coins_dist(self, block_height):
while sum(subset) < diff:
subset.append(O.pop())
excess = sum(subset) - diff
assert isinstance(excess, int)
if excess >= Vm:
O.append(excess)
else:
Expand All @@ -248,6 +249,7 @@ def is_negligible(self, coin, block_height):
in the worst case (when the fee rate is equal to the reserve rate).
Note: t1 is same as the lower bound of Vb.
"""
assert isinstance(coin["amount"], int)
# FIXME: What is a reasonable factor of a 'negligible coin'?
reserve_rate = self._feerate_reserve_per_vault(block_height)
t1 = reserve_rate * (float(P2WPKH_INPUT_SIZE) / 4) + self._feerate_to_fee(
Expand All @@ -262,6 +264,7 @@ def is_negligible(self, coin, block_height):

def refill(self, amount):
"""Refill the WT by generating a new feebump coin worth 'amount', with no allocation."""
assert isinstance(amount, int)
utxo = {
"idx": self.fbcoin_count,
"amount": amount,
Expand All @@ -271,7 +274,6 @@ def refill(self, amount):
self.fbcoin_count += 1
self.fbcoins.append(utxo)

# FIXME: should return the picked coins
def grab_coins_0(self, block_height):
"""Select coins to consume as inputs for the tx transaction,
remove them from P and V.
Expand All @@ -290,7 +292,6 @@ def grab_coins_0(self, block_height):

return total, num_inputs

# FIXME: should return the picked coins
def grab_coins_1(self, block_height):
"""Select coins to consume as inputs for the tx transaction,
remove them from P and V.
Expand Down Expand Up @@ -355,6 +356,7 @@ def grab_coins_2(self, block_height):
# loop over copy of the list since the remove() method changes list indexing
for coin in list(self.fbcoins):
if coin["processed"] == None:
assert isinstance(coin["amount"], int)
total_unprocessed += coin["amount"]
self._remove_coin(coin)
num_inputs += 1
Expand All @@ -366,6 +368,7 @@ def grab_coins_2(self, block_height):
for coin in list(self.fbcoins):
if coin["allocation"] == None and coin["amount"] not in fb_coins:
if block_height - coin["processed"] > old_age:
assert isinstance(coin["amount"], int)
total_unallocated += coin["amount"]
self._remove_coin(coin)
num_inputs += 1
Expand Down Expand Up @@ -417,6 +420,8 @@ def consolidate_fanout(self, block_height):
# Counter for number of outputs of the CF Tx
num_outputs = 0

# FIXME this doesn't re-create enough coins? Or maybe "there is always a top up afterward"?
# In any case this needs to make sure to not re-create less coins?
# Now create a distribution of new coins
num_new_reserves = total_to_consume // (sum(fb_coins))

Expand All @@ -430,7 +435,7 @@ def consolidate_fanout(self, block_height):
self.vaults = vaults_copy
return 0

for i in range(0, int(num_new_reserves)):
for i in range(0, num_new_reserves):
for x in fb_coins:
self.fbcoins.append(
{
Expand All @@ -449,17 +454,16 @@ def consolidate_fanout(self, block_height):
except (ValueError, KeyError):
feerate = self._feerate(block_height)

cf_tx_fee = (
TX_OVERHEAD_SIZE
+ num_outputs * P2WPKH_OUTPUT_SIZE
+ num_inputs * P2WPKH_INPUT_SIZE
) * feerate
cf_tx_size = cf_tx_size(num_inputs, num_outputs)
cf_tx_fee = int(cf_tx_size * feerate)

# If there is any remainder, use it first to pay the fee for this transaction
remainder = total_to_consume - (num_new_reserves * sum(fb_coins))
assert isinstance(remainder, int)

# Check if remainder would cover the fee for the tx, if so, add the remainder-fee to the final new coin
if remainder > cf_tx_fee:
# FIXME: i think this can be large
self.fbcoins[-1]["amount"] += remainder - cf_tx_fee
return cf_tx_fee
else:
Expand Down Expand Up @@ -491,12 +495,14 @@ def consolidate_fanout(self, block_height):
)

for coin in outputs:
assert isinstance(coin["amount"], int) and isinstance(cf_tx_fee, int)
# This coin is sufficient
if coin["amount"] >= cf_tx_fee:
# Update the amount in the actual self.fbcoins list, not in the copy
for c in self.fbcoins:
if c == coin:
c["amount"] -= cf_tx_fee
assert isinstance(c["amount"], int)
cf_tx_fee = 0
break # coins are unique, can stop when the correct one is found
break
Expand All @@ -518,7 +524,7 @@ def consolidate_fanout(self, block_height):
elif cf_tx_fee - P2WPKH_OUTPUT_SIZE * feerate > coin["amount"]:
self.fbcoins.remove(coin)
cf_tx_fee -= coin["amount"]
cf_tx_fee -= P2WPKH_OUTPUT_SIZE * feerate
cf_tx_fee -= int(P2WPKH_OUTPUT_SIZE * feerate)
num_outputs -= 1

if cf_tx_fee > 0:
Expand All @@ -530,18 +536,13 @@ def consolidate_fanout(self, block_height):

# note: cf_tx_fee used above to track how much each output contributed to the required fee,
# so it is recomputed here to return the actual fee paid
cf_size = (
10.75
+ num_outputs * P2WPKH_OUTPUT_SIZE
+ num_inputs * P2WPKH_INPUT_SIZE
)
if cf_size > MAX_TX_SIZE:
if cf_tx_size > MAX_TX_SIZE:
raise (
RuntimeError(
"The consolidate_fanout transactino is too large! Please be smarter when constructing it."
)
)
cf_tx_fee = cf_size * feerate
cf_tx_fee = cf_tx_size * feerate
return cf_tx_fee

def allocate(self, vaultID, amount, block_height):
Expand Down Expand Up @@ -693,6 +694,7 @@ def allocate(self, vaultID, amount, block_height):
)

# Successful new delegation and allocation!
assert isinstance(amount, int)
self.vaults.append(
{"id": vaultID, "amount": amount, "fee_reserve": fee_reserve}
)
Expand Down Expand Up @@ -747,6 +749,7 @@ def process_cancel(self, vaultID, block_height):
reserve = sorted(vault["fee_reserve"], key=lambda coin: coin["amount"])
try:
fbcoin = next(coin for coin in reserve if coin["amount"] > init_fee)
assert isinstance(fbcoin["amount"], int)
vault["fee_reserve"].remove(fbcoin)
self.fbcoins.remove(fbcoin)
init_fee -= fbcoin["amount"]
Expand Down
7 changes: 7 additions & 0 deletions Model/utils.py
Expand Up @@ -119,3 +119,10 @@
10: 2653,
},
}


def cf_tx_size(n_inputs, n_outputs):
"""Size of the consolidate-fanout transaction, in vbytes"""
return (
TX_OVERHEAD_SIZE + n_inputs * P2WPKH_INPUT_SIZE + n_outputs * P2WPKH_OUTPUT_SIZE
)

0 comments on commit 2376f1f

Please sign in to comment.