-
Notifications
You must be signed in to change notification settings - Fork 17
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
base: main
Are you sure you want to change the base?
Changes from all commits
c1adc83
5e32349
7eff7f0
10e407d
654b39e
32afc58
b417ab5
7e6fe86
3d70e4d
42af2e9
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 |
---|---|---|
|
@@ -16,6 +16,7 @@ package alonzo | |
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"math/big" | ||
"os" | ||
|
@@ -29,7 +30,7 @@ type AlonzoGenesis struct { | |
ExecutionPrices AlonzoGenesisExecutionPrices `json:"executionPrices"` | ||
MaxTxExUnits AlonzoGenesisExUnits `json:"maxTxExUnits"` | ||
MaxBlockExUnits AlonzoGenesisExUnits `json:"maxBlockExUnits"` | ||
CostModels map[string]map[string]int `json:"costModels"` | ||
CostModels map[string]interface{} `json:"costModels"` | ||
} | ||
|
||
func NewAlonzoGenesisFromReader(r io.Reader) (AlonzoGenesis, error) { | ||
|
@@ -39,6 +40,9 @@ func NewAlonzoGenesisFromReader(r io.Reader) (AlonzoGenesis, error) { | |
if err := dec.Decode(&ret); err != nil { | ||
return ret, err | ||
} | ||
if err := ret.NormalizeCostModels(); err != nil { | ||
return ret, err | ||
} | ||
return ret, nil | ||
} | ||
|
||
|
@@ -76,3 +80,42 @@ func (r *AlonzoGenesisExecutionPricesRat) UnmarshalJSON(data []byte) error { | |
r.Rat = big.NewRat(tmpData.Numerator, tmpData.Denominator) | ||
return nil | ||
} | ||
|
||
func (a *AlonzoGenesis) NormalizeCostModels() error { | ||
if a.CostModels == nil { | ||
return nil | ||
} | ||
|
||
normalized := make(map[string]map[string]int) | ||
for version, model := range a.CostModels { | ||
if modelMap, ok := model.(map[string]interface{}); ok { | ||
versionMap := make(map[string]int) | ||
for k, v := range modelMap { | ||
switch val := v.(type) { | ||
case float64: | ||
versionMap[k] = int(val) | ||
case int: | ||
versionMap[k] = val | ||
case json.Number: | ||
intVal, err := val.Int64() | ||
if err != nil { | ||
floatVal, err := val.Float64() | ||
if err != nil { | ||
return fmt.Errorf("invalid number in cost model: %v", val) | ||
} | ||
intVal = int64(floatVal) | ||
} | ||
versionMap[k] = int(intVal) | ||
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. Do we encounter all 3 of these types when parsing example Alonzo genesis files, or is this just covering our bases? |
||
default: | ||
return fmt.Errorf("invalid cost model value type: %T", v) | ||
} | ||
} | ||
normalized[version] = versionMap | ||
} | ||
} | ||
a.CostModels = make(map[string]interface{}) | ||
for k, v := range normalized { | ||
a.CostModels[k] = v | ||
} | ||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ | |
package alonzo | ||
|
||
import ( | ||
"fmt" | ||
"math" | ||
|
||
"github.com/blinklabs-io/gouroboros/cbor" | ||
|
@@ -23,6 +24,20 @@ import ( | |
cardano "github.com/utxorpc/go-codegen/utxorpc/v1alpha/cardano" | ||
) | ||
|
||
// Constants for Plutus version mapping | ||
const ( | ||
PlutusV1Key uint = 0 | ||
PlutusV2Key uint = 1 | ||
PlutusV3Key uint = 2 | ||
) | ||
|
||
// Expected parameter counts for validation | ||
var plutusParamCounts = map[uint]int{ | ||
PlutusV1Key: 166, | ||
PlutusV2Key: 175, | ||
PlutusV3Key: 187, | ||
} | ||
|
||
type AlonzoProtocolParameters struct { | ||
cbor.StructAsArray | ||
MinFeeA uint | ||
|
@@ -134,10 +149,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 +167,78 @@ 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 { | ||
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. This is going to get split back to 2 lines the next time that we run |
||
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[uint][]int64) | ||
|
||
for versionStr, model := range genesis.CostModels { | ||
key, ok := plutusVersionToKey(versionStr) | ||
if !ok { | ||
continue | ||
} | ||
|
||
var values []int64 | ||
switch v := model.(type) { | ||
case map[string]interface{}: | ||
maxIndex := 0 | ||
// Find maximum parameter index | ||
for paramName := range v { | ||
var index int | ||
if _, err := fmt.Sscanf(paramName, "param%d", &index); err == nil && index > maxIndex { | ||
maxIndex = index | ||
} | ||
} | ||
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. This seems to be expecting a structure like this, but I'm not seeing where that would come from outside of the tests. It seems that it would be easier to use
|
||
values = make([]int64, maxIndex) | ||
for paramName, val := range v { | ||
var index int | ||
if _, err := fmt.Sscanf(paramName, "param%d", &index); err == nil && index > 0 { | ||
if intVal, ok := val.(float64); ok { | ||
values[index-1] = int64(intVal) | ||
} | ||
} | ||
} | ||
|
||
case []interface{}: | ||
values = make([]int64, len(v)) | ||
for i, val := range v { | ||
if intVal, ok := val.(float64); ok { | ||
values[i] = int64(intVal) | ||
} | ||
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. Will this only ever contain |
||
} | ||
|
||
default: | ||
return fmt.Errorf("invalid cost model format for %s", versionStr) | ||
} | ||
if expected, ok := plutusParamCounts[key]; ok && len(values) != expected { | ||
return fmt.Errorf("invalid parameter count for %s: expected %d, got %d", | ||
versionStr, expected, len(values)) | ||
} | ||
|
||
p.CostModels[key] = values | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// Helper to convert Plutus version string to key | ||
func plutusVersionToKey(version string) (uint, bool) { | ||
switch version { | ||
case "PlutusV1": | ||
return PlutusV1Key, true | ||
case "PlutusV2": | ||
return PlutusV2Key, true | ||
case "PlutusV3": | ||
return PlutusV3Key, true | ||
default: | ||
return 0, false | ||
} | ||
} | ||
|
||
type AlonzoProtocolParameterUpdate struct { | ||
|
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 field type is going to force us to do something like
foo.CostModels.(map[string]int)["foo"]
every time that we access something from it, which isn't ideal. What we probably want is a separateCostModel
type with a customUnmarshalJSON()
function that works similarly toNormalizeCostModels()
below.