Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
287 changes: 208 additions & 79 deletions ledger/alonzo/pparams.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,30 @@
package alonzo

import (
"fmt"
"math"
"sort"
"strings"

"github.com/blinklabs-io/gouroboros/cbor"
"github.com/blinklabs-io/gouroboros/ledger/common"
"github.com/blinklabs-io/gouroboros/ledger/mary"
cardano "github.com/utxorpc/go-codegen/utxorpc/v1alpha/cardano"
)

type PlutusVersion string

const (
PlutusV1 PlutusVersion = "PlutusV1"
PlutusV2 PlutusVersion = "PlutusV2"
PlutusV3 PlutusVersion = "PlutusV3"
)

type CostModel struct {
Version PlutusVersion
Parameters map[string]int64
Order []string
}

type AlonzoProtocolParameters struct {
cbor.StructAsArray
MinFeeA uint
Expand All @@ -44,7 +60,7 @@ type AlonzoProtocolParameters struct {
MinUtxoValue uint
MinPoolCost uint64
AdaPerUtxoByte uint64
CostModels map[uint][]int64
CostModels map[PlutusVersion]*CostModel
Copy link
Contributor

Choose a reason for hiding this comment

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

This is (probably) going to break CBOR decoding of Alonzo protocol params. The previous struct field definition matches the structure of the CBOR data.

ExecutionCosts common.ExUnitPrice
MaxTxExUnits common.ExUnits
MaxBlockExUnits common.ExUnits
Expand Down Expand Up @@ -112,7 +128,7 @@ func (p *AlonzoProtocolParameters) Update(
p.AdaPerUtxoByte = *paramUpdate.AdaPerUtxoByte
}
if paramUpdate.CostModels != nil {
p.CostModels = paramUpdate.CostModels
p.convertLegacyCostModels(paramUpdate.CostModels)
}
if paramUpdate.ExecutionCosts != nil {
p.ExecutionCosts = *paramUpdate.ExecutionCosts
Expand All @@ -134,10 +150,12 @@ func (p *AlonzoProtocolParameters) Update(
}
}

func (p *AlonzoProtocolParameters) UpdateFromGenesis(genesis *AlonzoGenesis) {
func (p *AlonzoProtocolParameters) UpdateFromGenesis(genesis *AlonzoGenesis) error {
if genesis == nil {
return
return nil
}

// Common parameter updates
p.AdaPerUtxoByte = genesis.LovelacePerUtxoWord / 8
p.MaxValueSize = genesis.MaxValueSize
p.CollateralPercentage = genesis.CollateralPercentage
Expand All @@ -150,16 +168,115 @@ func (p *AlonzoProtocolParameters) UpdateFromGenesis(genesis *AlonzoGenesis) {
Memory: uint64(genesis.MaxBlockExUnits.Mem),
Steps: uint64(genesis.MaxBlockExUnits.Steps),
}
if genesis.ExecutionPrices.Mem != nil &&
genesis.ExecutionPrices.Steps != nil {

if genesis.ExecutionPrices.Mem != nil && genesis.ExecutionPrices.Steps != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is going to get split back to 2 lines the next time that we run golines on this repo

p.ExecutionCosts = common.ExUnitPrice{
MemPrice: &cbor.Rat{Rat: genesis.ExecutionPrices.Mem.Rat},
StepPrice: &cbor.Rat{Rat: genesis.ExecutionPrices.Steps.Rat},
}
}
// TODO: cost models (#852)
// We have 150+ string values to map to array indexes
// CostModels map[string]map[string]int

if genesis.CostModels != nil {
p.CostModels = make(map[PlutusVersion]*CostModel)

for lang, model := range genesis.CostModels {
version, ok := toPlutusVersion(lang)
if !ok {
continue
}

params := make(map[string]int64)
order := make([]string, 0, len(model))

// Since model is now map[string]int, we don't need type assertions
for name, val := range model {
params[name] = int64(val) // Convert int to int64
order = append(order, name)
}

// Sort keys alphabetically (maintains consistency with original behavior)
sort.Strings(order)

p.CostModels[version] = &CostModel{
Version: version,
Parameters: params,
Order: order,
}
}
}
return nil
}

func (p *AlonzoProtocolParameters) convertLegacyCostModels(legacyModels map[uint][]int64) {
p.CostModels = make(map[PlutusVersion]*CostModel)

for langKey, values := range legacyModels {
var version PlutusVersion
switch langKey {
case 0:
version = PlutusV1
case 1:
version = PlutusV2
case 2:
version = PlutusV3
default:
continue
}

params := make(map[string]int64)
order := make([]string, len(values))
for i, val := range values {
name := fmt.Sprintf("param%d", i)
params[name] = val
order[i] = name
}

p.CostModels[version] = &CostModel{
Version: version,
Parameters: params,
Order: order,
}
}
}

func toPlutusVersion(key string) (PlutusVersion, bool) {
switch strings.ToLower(key) {
case "plutus:v1", "plutusv1":
return PlutusV1, true
case "plutus:v2", "plutusv2":
return PlutusV2, true
case "plutus:v3", "plutusv3":
return PlutusV3, true
Copy link
Contributor

Choose a reason for hiding this comment

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

Where are any of these forms used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixing the forms

default:
return "", false
}
}

func (p *AlonzoProtocolParameters) ToLegacyCostModels() map[uint][]int64 {
legacyModels := make(map[uint][]int64)

for version, model := range p.CostModels {
var langKey uint
switch version {
case PlutusV1:
langKey = 0
case PlutusV2:
langKey = 1
case PlutusV3:
langKey = 2
default:
continue
}

// Convert ordered parameters back to list format
values := make([]int64, len(model.Order))
for i, name := range model.Order {
values[i] = model.Parameters[name]
}
legacyModels[langKey] = values
}

return legacyModels
}

type AlonzoProtocolParameterUpdate struct {
Expand Down Expand Up @@ -205,33 +322,86 @@ func (u *AlonzoProtocolParameterUpdate) UnmarshalCBOR(cborData []byte) error {
}

func (p *AlonzoProtocolParameters) Utxorpc() *cardano.PParams {
// sanity check
if p.A0.Num().Int64() > math.MaxInt32 ||
p.A0.Denom().Int64() < 0 ||
p.A0.Denom().Int64() > math.MaxUint32 {
if p == nil {
return nil
}
if p.Rho.Num().Int64() > math.MaxInt32 ||
p.Rho.Denom().Int64() < 0 ||
p.Rho.Denom().Int64() > math.MaxUint32 {
return nil

// Safe conversion helper functions
safeInt64ToInt32 := func(val int64) (int32, bool) {
if val < math.MinInt32 || val > math.MaxInt32 {
return 0, false
}
return int32(val), true
}
if p.Tau.Num().Int64() > math.MaxInt32 ||
p.Tau.Denom().Int64() < 0 ||
p.Tau.Denom().Int64() > math.MaxUint32 {
return nil

safeUintToUint32 := func(val uint) (uint32, bool) {
if val > math.MaxUint32 {
return 0, false
}
return uint32(val), true
}
if p.ExecutionCosts.MemPrice.Num().Int64() > math.MaxInt32 ||
p.ExecutionCosts.MemPrice.Denom().Int64() < 0 ||
p.ExecutionCosts.MemPrice.Denom().Int64() > math.MaxUint32 {

// Convert protocol version
protocolMajor, ok1 := safeUintToUint32(p.ProtocolMajor)
protocolMinor, ok2 := safeUintToUint32(p.ProtocolMinor)
if !ok1 || !ok2 {
return nil
}
if p.ExecutionCosts.StepPrice.Num().Int64() > math.MaxInt32 ||
p.ExecutionCosts.StepPrice.Denom().Int64() < 0 ||
p.ExecutionCosts.StepPrice.Denom().Int64() > math.MaxUint32 {

// Convert rational numbers
convertRat := func(rat *cbor.Rat) *cardano.RationalNumber {
if rat == nil || rat.Rat == nil {
return nil
}
num, numOk := safeInt64ToInt32(rat.Num().Int64())
denom64 := rat.Denom().Int64()
if denom64 <= 0 || denom64 > math.MaxUint32 {
return nil
}
denom := uint32(denom64)
if !numOk {
return nil
}
return &cardano.RationalNumber{
Numerator: num,
Denominator: denom,
}
}

a0 := convertRat(p.A0)
rho := convertRat(p.Rho)
tau := convertRat(p.Tau)
memPrice := convertRat(p.ExecutionCosts.MemPrice)
stepPrice := convertRat(p.ExecutionCosts.StepPrice)

if a0 == nil || rho == nil || tau == nil || memPrice == nil || stepPrice == nil {
return nil
}
// #nosec G115

// Convert cost models
costModels := &cardano.CostModels{}
if p.CostModels != nil {
costModels.PlutusV1 = &cardano.CostModel{Values: []int64{}}
costModels.PlutusV2 = &cardano.CostModel{Values: []int64{}}
costModels.PlutusV3 = &cardano.CostModel{Values: []int64{}}

for version, model := range p.CostModels {
values := make([]int64, len(model.Order))
for i, name := range model.Order {
values[i] = model.Parameters[name]
}

switch version {
case PlutusV1:
costModels.PlutusV1.Values = values
case PlutusV2:
costModels.PlutusV2.Values = values
case PlutusV3:
costModels.PlutusV3.Values = values
}
}
}

return &cardano.PParams{
CoinsPerUtxoByte: p.AdaPerUtxoByte,
MaxTxSize: uint64(p.MaxTxSize),
Expand All @@ -243,38 +413,21 @@ func (p *AlonzoProtocolParameters) Utxorpc() *cardano.PParams {
PoolDeposit: uint64(p.PoolDeposit),
PoolRetirementEpochBound: uint64(p.MaxEpoch),
DesiredNumberOfPools: uint64(p.NOpt),
PoolInfluence: &cardano.RationalNumber{
Numerator: int32(p.A0.Num().Int64()),
Denominator: uint32(p.A0.Denom().Int64()),
},
MonetaryExpansion: &cardano.RationalNumber{
Numerator: int32(p.Rho.Num().Int64()),
Denominator: uint32(p.Rho.Denom().Int64()),
},
TreasuryExpansion: &cardano.RationalNumber{
Numerator: int32(p.Tau.Num().Int64()),
Denominator: uint32(p.Tau.Denom().Int64()),
},
MinPoolCost: p.MinPoolCost,
PoolInfluence: a0,
MonetaryExpansion: rho,
TreasuryExpansion: tau,
MinPoolCost: p.MinPoolCost,
ProtocolVersion: &cardano.ProtocolVersion{
Major: uint32(p.ProtocolMajor),
Minor: uint32(p.ProtocolMinor),
Major: protocolMajor,
Minor: protocolMinor,
},
MaxValueSize: uint64(p.MaxValueSize),
CollateralPercentage: uint64(p.CollateralPercentage),
MaxCollateralInputs: uint64(p.MaxCollateralInputs),
CostModels: common.ConvertToUtxorpcCardanoCostModels(
p.CostModels,
),
CostModels: costModels,
Prices: &cardano.ExPrices{
Memory: &cardano.RationalNumber{
Numerator: int32(p.ExecutionCosts.MemPrice.Num().Int64()),
Denominator: uint32(p.ExecutionCosts.MemPrice.Denom().Int64()),
},
Steps: &cardano.RationalNumber{
Numerator: int32(p.ExecutionCosts.StepPrice.Num().Int64()),
Denominator: uint32(p.ExecutionCosts.StepPrice.Denom().Int64()),
},
Memory: memPrice,
Steps: stepPrice,
},
MaxExecutionUnitsPerTransaction: &cardano.ExUnits{
Memory: p.MaxTxExUnits.Memory,
Expand All @@ -286,27 +439,3 @@ func (p *AlonzoProtocolParameters) Utxorpc() *cardano.PParams {
},
}
}

func UpgradePParams(
prevPParams mary.MaryProtocolParameters,
) AlonzoProtocolParameters {
return AlonzoProtocolParameters{
MinFeeA: prevPParams.MinFeeA,
MinFeeB: prevPParams.MinFeeB,
MaxBlockBodySize: prevPParams.MaxBlockBodySize,
MaxTxSize: prevPParams.MaxTxSize,
MaxBlockHeaderSize: prevPParams.MaxBlockHeaderSize,
KeyDeposit: prevPParams.KeyDeposit,
PoolDeposit: prevPParams.PoolDeposit,
MaxEpoch: prevPParams.MaxEpoch,
NOpt: prevPParams.NOpt,
A0: prevPParams.A0,
Rho: prevPParams.Rho,
Tau: prevPParams.Tau,
Decentralization: prevPParams.Decentralization,
ExtraEntropy: prevPParams.ExtraEntropy,
ProtocolMajor: prevPParams.ProtocolMajor,
ProtocolMinor: prevPParams.ProtocolMinor,
MinUtxoValue: prevPParams.MinUtxoValue,
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did you remove this function? We use this in dingo

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Apologies, I removed this function unintentionally while resolving the conflicts. fixing it

Loading
Loading