Skip to content
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

fix: use correct TX size calc for min-fee in Alonzo and later #967

Merged
merged 1 commit into from
Mar 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 4 additions & 1 deletion ledger/allegra/allegra.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,10 @@ func (t *AllegraTransaction) Cbor() []byte {
tmpObj = append(tmpObj, nil)
}
// This should never fail, since we're only encoding a list and a bool value
cborData, _ = cbor.Encode(&tmpObj)
cborData, err := cbor.Encode(&tmpObj)
if err != nil {
panic("CBOR encoding that should never fail has failed: " + err.Error())
}
return cborData
}

Expand Down
15 changes: 9 additions & 6 deletions ledger/alonzo/alonzo.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func (b *AlonzoBlock) Transactions() []common.Transaction {
Body: b.TransactionBodies[idx],
WitnessSet: b.TransactionWitnessSets[idx],
TxMetadata: b.TransactionMetadataSet[uint(idx)],
IsTxValid: !invalidTxMap[uint(idx)],
TxIsValid: !invalidTxMap[uint(idx)],
}
}
return ret
Expand Down Expand Up @@ -358,7 +358,7 @@ type AlonzoTransaction struct {
cbor.DecodeStoreCbor
Body AlonzoTransactionBody
WitnessSet AlonzoTransactionWitnessSet
IsTxValid bool
TxIsValid bool
TxMetadata *cbor.LazyValue
}

Expand Down Expand Up @@ -459,7 +459,7 @@ func (t AlonzoTransaction) Metadata() *cbor.LazyValue {
}

func (t AlonzoTransaction) IsValid() bool {
return t.IsTxValid
return t.TxIsValid
}

func (t AlonzoTransaction) Consumed() []common.TransactionInput {
Expand Down Expand Up @@ -496,7 +496,7 @@ func (t AlonzoTransaction) Witnesses() common.TransactionWitnessSet {
func (t *AlonzoTransaction) Cbor() []byte {
// Return stored CBOR if we have any
cborData := t.DecodeStoreCbor.Cbor()
if cborData != nil {
if len(cborData) > 0 {
return cborData[:]
}
// Return immediately if the body CBOR is also empty, which implies an empty TX object
Expand All @@ -508,15 +508,18 @@ func (t *AlonzoTransaction) Cbor() []byte {
tmpObj := []any{
cbor.RawMessage(t.Body.Cbor()),
cbor.RawMessage(t.WitnessSet.Cbor()),
t.IsValid,
t.TxIsValid,
}
if t.TxMetadata != nil {
tmpObj = append(tmpObj, cbor.RawMessage(t.TxMetadata.Cbor()))
} else {
tmpObj = append(tmpObj, nil)
}
// This should never fail, since we're only encoding a list and a bool value
cborData, _ = cbor.Encode(&tmpObj)
cborData, err := cbor.Encode(&tmpObj)
if err != nil {
panic("CBOR encoding that should never fail has failed: " + err.Error())
}
return cborData
}

Expand Down
48 changes: 39 additions & 9 deletions ledger/alonzo/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,16 +133,17 @@ func UtxoValidateFeeTooSmallUtxo(
ls common.LedgerState,
pp common.ProtocolParameters,
) error {
tmpPparams, ok := pp.(*AlonzoProtocolParameters)
if !ok {
return errors.New("pparams are not expected type")
minFee, err := MinFeeTx(tx, pp)
if err != nil {
return err
}
if tx.Fee() >= minFee {
return nil
}
return shelley.FeeTooSmallUtxoError{
Provided: tx.Fee(),
Min: minFee,
}
return shelley.UtxoValidateFeeTooSmallUtxo(
tx,
slot,
ls,
&tmpPparams.ShelleyProtocolParameters,
)
}

// UtxoValidateInsufficientCollateral ensures that there is sufficient collateral provided
Expand Down Expand Up @@ -328,3 +329,32 @@ func UtxoValidateMaxTxSizeUtxo(
&tmpPparams.ShelleyProtocolParameters,
)
}

// MinFeeTx calculates the minimum required fee for a transaction based on protocol parameters
func MinFeeTx(
tx common.Transaction,
pparams common.ProtocolParameters,
) (uint64, error) {
tmpPparams, ok := pparams.(*AlonzoProtocolParameters)
if !ok {
return 0, errors.New("pparams are not expected type")
}
txBytes := tx.Cbor()
if len(txBytes) == 0 {
var err error
txBytes, err = cbor.Encode(tx)
if err != nil {
return 0, err
}
}
// We calculate fee based on the pre-Alonzo TX format, which does not include the 'valid' field
// Instead of having a separate helper to build the CBOR in the custom format, we subtract 1 since
// a boolean is always represented by a single byte in CBOR
txSize := max(0, len(txBytes)-1)
minFee := uint64(
// The TX size can never be negative, so casting to uint is safe
//nolint:gosec
(tmpPparams.MinFeeA * uint(txSize)) + tmpPparams.MinFeeB,
)
return minFee, nil
}
3 changes: 2 additions & 1 deletion ledger/alonzo/rules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,8 @@ func TestUtxoValidateFeeTooSmallUtxo(t *testing.T) {
var testExactFee uint64 = 74
var testBelowFee uint64 = 73
var testAboveFee uint64 = 75
testTxCbor, _ := hex.DecodeString("abcdef")
// NOTE: this is length 4, but 3 will be used in the calculations
testTxCbor, _ := hex.DecodeString("abcdef01")
testTx := &alonzo.AlonzoTransaction{
Body: alonzo.AlonzoTransactionBody{
MaryTransactionBody: mary.MaryTransactionBody{
Expand Down
13 changes: 8 additions & 5 deletions ledger/babbage/babbage.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func (b *BabbageBlock) Transactions() []common.Transaction {
Body: b.TransactionBodies[idx],
WitnessSet: b.TransactionWitnessSets[idx],
TxMetadata: b.TransactionMetadataSet[uint(idx)],
IsTxValid: !invalidTxMap[uint(idx)],
TxIsValid: !invalidTxMap[uint(idx)],
}
}
return ret
Expand Down Expand Up @@ -511,7 +511,7 @@ type BabbageTransaction struct {
cbor.DecodeStoreCbor
Body BabbageTransactionBody
WitnessSet BabbageTransactionWitnessSet
IsTxValid bool
TxIsValid bool
TxMetadata *cbor.LazyValue
}

Expand Down Expand Up @@ -612,7 +612,7 @@ func (t BabbageTransaction) Metadata() *cbor.LazyValue {
}

func (t BabbageTransaction) IsValid() bool {
return t.IsTxValid
return t.TxIsValid
}

func (t BabbageTransaction) Consumed() []common.TransactionInput {
Expand Down Expand Up @@ -668,15 +668,18 @@ func (t *BabbageTransaction) Cbor() []byte {
tmpObj := []any{
cbor.RawMessage(t.Body.Cbor()),
cbor.RawMessage(t.WitnessSet.Cbor()),
t.IsValid,
t.TxIsValid,
}
if t.TxMetadata != nil {
tmpObj = append(tmpObj, cbor.RawMessage(t.TxMetadata.Cbor()))
} else {
tmpObj = append(tmpObj, nil)
}
// This should never fail, since we're only encoding a list and a bool value
cborData, _ = cbor.Encode(&tmpObj)
cborData, err := cbor.Encode(&tmpObj)
if err != nil {
panic("CBOR encoding that should never fail has failed: " + err.Error())
}
return cborData
}

Expand Down
8 changes: 7 additions & 1 deletion ledger/babbage/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -463,8 +463,14 @@ func MinFeeTx(
return 0, err
}
}
// We calculate fee based on the pre-Alonzo TX format, which does not include the 'valid' field
// Instead of having a separate helper to build the CBOR in the custom format, we subtract 1 since
// a boolean is always represented by a single byte in CBOR
txSize := max(0, len(txBytes)-1)
minFee := uint64(
(tmpPparams.MinFeeA * uint(len(txBytes))) + tmpPparams.MinFeeB,
// The TX size can never be negative, so casting to uint is safe
//nolint:gosec
(tmpPparams.MinFeeA * uint(txSize)) + tmpPparams.MinFeeB,
)
return minFee, nil
}
Expand Down
3 changes: 2 additions & 1 deletion ledger/babbage/rules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,8 @@ func TestUtxoValidateFeeTooSmallUtxo(t *testing.T) {
var testExactFee uint64 = 74
var testBelowFee uint64 = 73
var testAboveFee uint64 = 75
testTxCbor, _ := hex.DecodeString("abcdef")
// NOTE: this is length 4, but 3 will be used in the calculations
testTxCbor, _ := hex.DecodeString("abcdef01")
testTx := &babbage.BabbageTransaction{
Body: babbage.BabbageTransactionBody{
AlonzoTransactionBody: alonzo.AlonzoTransactionBody{
Expand Down
13 changes: 8 additions & 5 deletions ledger/conway/conway.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func (b *ConwayBlock) Transactions() []common.Transaction {
Body: b.TransactionBodies[idx],
WitnessSet: b.TransactionWitnessSets[idx],
TxMetadata: b.TransactionMetadataSet[uint(idx)],
IsTxValid: !invalidTxMap[uint(idx)],
TxIsValid: !invalidTxMap[uint(idx)],
}
}
return ret
Expand Down Expand Up @@ -313,7 +313,7 @@ type ConwayTransaction struct {
cbor.DecodeStoreCbor
Body ConwayTransactionBody
WitnessSet ConwayTransactionWitnessSet
IsTxValid bool
TxIsValid bool
TxMetadata *cbor.LazyValue
}

Expand Down Expand Up @@ -414,7 +414,7 @@ func (t ConwayTransaction) Metadata() *cbor.LazyValue {
}

func (t ConwayTransaction) IsValid() bool {
return t.IsTxValid
return t.TxIsValid
}

func (t ConwayTransaction) Consumed() []common.TransactionInput {
Expand Down Expand Up @@ -470,15 +470,18 @@ func (t *ConwayTransaction) Cbor() []byte {
tmpObj := []any{
cbor.RawMessage(t.Body.Cbor()),
cbor.RawMessage(t.WitnessSet.Cbor()),
t.IsValid,
t.TxIsValid,
}
if t.TxMetadata != nil {
tmpObj = append(tmpObj, cbor.RawMessage(t.TxMetadata.Cbor()))
} else {
tmpObj = append(tmpObj, nil)
}
// This should never fail, since we're only encoding a list and a bool value
cborData, _ = cbor.Encode(&tmpObj)
cborData, err := cbor.Encode(&tmpObj)
if err != nil {
panic("CBOR encoding that should never fail has failed: " + err.Error())
}
return cborData
}

Expand Down
8 changes: 7 additions & 1 deletion ledger/conway/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,8 +464,14 @@ func MinFeeTx(
return 0, err
}
}
// We calculate fee based on the pre-Alonzo TX format, which does not include the 'valid' field
// Instead of having a separate helper to build the CBOR in the custom format, we subtract 1 since
// a boolean is always represented by a single byte in CBOR
txSize := max(0, len(txBytes)-1)
minFee := uint64(
(tmpPparams.MinFeeA * uint(len(txBytes))) + tmpPparams.MinFeeB,
// The TX size can never be negative, so casting to uint is safe
//nolint:gosec
(tmpPparams.MinFeeA * uint(txSize)) + tmpPparams.MinFeeB,
)
return minFee, nil
}
Expand Down
3 changes: 2 additions & 1 deletion ledger/conway/rules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,8 @@ func TestUtxoValidateFeeTooSmallUtxo(t *testing.T) {
var testExactFee uint64 = 74
var testBelowFee uint64 = 73
var testAboveFee uint64 = 75
testTxCbor, _ := hex.DecodeString("abcdef")
// NOTE: this is length 4, but 3 will be used in the calculations
testTxCbor, _ := hex.DecodeString("abcdef01")
testTx := &conway.ConwayTransaction{
Body: conway.ConwayTransactionBody{
BabbageTransactionBody: babbage.BabbageTransactionBody{
Expand Down
5 changes: 4 additions & 1 deletion ledger/mary/mary.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,10 @@ func (t *MaryTransaction) Cbor() []byte {
tmpObj = append(tmpObj, nil)
}
// This should never fail, since we're only encoding a list and a bool value
cborData, _ = cbor.Encode(&tmpObj)
cborData, err := cbor.Encode(&tmpObj)
if err != nil {
panic("CBOR encoding that should never fail has failed: " + err.Error())
}
return cborData
}

Expand Down
5 changes: 4 additions & 1 deletion ledger/shelley/shelley.go
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,10 @@ func (t *ShelleyTransaction) Cbor() []byte {
tmpObj = append(tmpObj, nil)
}
// This should never fail, since we're only encoding a list and a bool value
cborData, _ = cbor.Encode(&tmpObj)
cborData, err := cbor.Encode(&tmpObj)
if err != nil {
panic("CBOR encoding that should never fail has failed: " + err.Error())
}
return cborData
}

Expand Down
Loading