-
Notifications
You must be signed in to change notification settings - Fork 20
feat: changes to support cost models while updating Alonzo protocols through genesis config #984
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
Changes from 8 commits
c1adc83
5e32349
7eff7f0
10e407d
654b39e
32afc58
b417ab5
7e6fe86
3d70e4d
42af2e9
61cd643
4b53e53
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -44,7 +60,7 @@ type AlonzoProtocolParameters struct { | |
MinUtxoValue uint | ||
MinPoolCost uint64 | ||
AdaPerUtxoByte uint64 | ||
CostModels map[uint][]int64 | ||
CostModels map[PlutusVersion]*CostModel | ||
ExecutionCosts common.ExUnitPrice | ||
MaxTxExUnits common.ExUnits | ||
MaxBlockExUnits common.ExUnits | ||
|
@@ -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 | ||
|
@@ -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 | ||
|
@@ -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 { | ||
|
||
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 | ||
|
||
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 { | ||
|
@@ -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), | ||
|
@@ -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, | ||
|
@@ -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, | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why did you remove this function? We use this in dingo There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Apologies, I removed this function unintentionally while resolving the conflicts. fixing it |
There was a problem hiding this comment.
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.