Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion transaction/fee_model/sats_per_kb.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,9 @@ func (s *SatoshisPerKilobyte) ComputeFee(tx *transaction.Transaction) (uint64, e
size += len(*o.LockingScript)
}
size += 4
return (uint64(math.Ceil(float64(size)/1000)) * s.Satoshis), nil
return calculateFee(size, s.Satoshis), nil
}

func calculateFee(txSizeBytes int, satoshisPerKB uint64) uint64 {
return uint64(math.Ceil(float64(txSizeBytes) / 1000 * float64(satoshisPerKB)))
Copy link

@kuba-4chain kuba-4chain Nov 13, 2025

Choose a reason for hiding this comment

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

This changes the fee calculation. Assuming the fee rate is 100sat/KB, if the math.Ceil() is over the entire equation, it allows for the fee to be lower than 100 sats for transactions of size lower than 1 KB. If the math.Ceil() is only over the txSizeBytes / 1000, then the fee will never be lower than 100 sats

}
74 changes: 74 additions & 0 deletions transaction/fee_model/sats_per_kb_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package feemodel

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestCalculateFee(t *testing.T) {
tests := []struct {
name string
txSize int
satoshisPerKB uint64
expectedFee uint64
description string
}{
{
name: "240 bytes at 100 sats/KB",
txSize: 240,
satoshisPerKB: 100,
expectedFee: 24,
description: "240/1000 * 100 = 24 - tests the bug where casting happened before multiplication",
},
{
name: "240 bytes at 1 sat/KB",
txSize: 240,
satoshisPerKB: 1,
expectedFee: 1,
description: "Edge case that would pass even with buggy implementation",
},
{
name: "240 bytes at 10 sats/KB",
txSize: 240,
satoshisPerKB: 10,
expectedFee: 3,
description: "240/1000 * 10 = 2.4, ceil = 3",
},
{
name: "250 bytes at 500 sats/KB",
txSize: 250,
satoshisPerKB: 500,
expectedFee: 125,
description: "250/1000 * 500 = 125",
},
{
name: "1000 bytes at 100 sats/KB",
txSize: 1000,
satoshisPerKB: 100,
expectedFee: 100,
description: "1000/1000 * 100 = 100",
},
{
name: "1500 bytes at 100 sats/KB",
txSize: 1500,
satoshisPerKB: 100,
expectedFee: 150,
description: "1500/1000 * 100 = 150",
},
{
name: "1500 bytes at 500 sats/KB",
txSize: 1500,
satoshisPerKB: 500,
expectedFee: 750,
description: "1500/1000 * 500 = 750",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fee := calculateFee(tt.txSize, tt.satoshisPerKB)
require.Equal(t, tt.expectedFee, fee, tt.description)
})
}
}
Loading