diff --git a/ledger/alonzo/alonzo.go b/ledger/alonzo/alonzo.go index 71ae5ffb..2823e16d 100644 --- a/ledger/alonzo/alonzo.go +++ b/ledger/alonzo/alonzo.go @@ -475,48 +475,6 @@ func (t *AlonzoTransaction) Utxorpc() *utxorpc.Tx { return t.Body.Utxorpc() } -type ExUnit struct { - cbor.StructAsArray - Mem uint - Steps uint -} - -type ExUnitPrice struct { - cbor.StructAsArray - MemPrice uint - StepPrice uint -} - -type AlonzoProtocolParameters struct { - mary.MaryProtocolParameters - MinPoolCost uint - AdaPerUtxoByte uint - CostModels uint - ExecutionCosts uint - MaxTxExUnits uint - MaxBlockExUnits uint - MaxValueSize uint - CollateralPercentage uint - MaxCollateralInputs uint -} - -type AlonzoProtocolParameterUpdate struct { - mary.MaryProtocolParameterUpdate - MinPoolCost uint `cbor:"16,keyasint"` - AdaPerUtxoByte uint `cbor:"17,keyasint"` - CostModels map[uint][]uint `cbor:"18,keyasint"` - ExecutionCosts *ExUnitPrice `cbor:"19,keyasint"` - MaxTxExUnits *ExUnit `cbor:"20,keyasint"` - MaxBlockExUnits *ExUnit `cbor:"21,keyasint"` - MaxValueSize uint `cbor:"22,keyasint"` - CollateralPercentage uint `cbor:"23,keyasint"` - MaxCollateralInputs uint `cbor:"24,keyasint"` -} - -func (u *AlonzoProtocolParameterUpdate) UnmarshalCBOR(data []byte) error { - return u.UnmarshalCbor(data, u) -} - func NewAlonzoBlockFromCbor(data []byte) (*AlonzoBlock, error) { var alonzoBlock AlonzoBlock if _, err := cbor.Decode(data, &alonzoBlock); err != nil { diff --git a/ledger/alonzo/genesis.go b/ledger/alonzo/genesis.go index aeeba80f..98b7035c 100644 --- a/ledger/alonzo/genesis.go +++ b/ledger/alonzo/genesis.go @@ -17,17 +17,18 @@ package alonzo import ( "encoding/json" "io" + "math/big" "os" ) type AlonzoGenesis struct { LovelacePerUtxoWord uint64 `json:"lovelacePerUTxOWord"` - MaxValueSize int - CollateralPercentage int - MaxCollateralInputs int - ExecutionPrices map[string]map[string]int - MaxTxExUnits map[string]int - MaxBlockExUnits map[string]int + MaxValueSize uint + CollateralPercentage uint + MaxCollateralInputs uint + ExecutionPrices AlonzoGenesisExecutionPrices + MaxTxExUnits AlonzoGenesisExUnits + MaxBlockExUnits AlonzoGenesisExUnits CostModels map[string]map[string]int } @@ -49,3 +50,29 @@ func NewAlonzoGenesisFromFile(path string) (AlonzoGenesis, error) { defer f.Close() return NewAlonzoGenesisFromReader(f) } + +type AlonzoGenesisExUnits struct { + Mem uint `json:"exUnitsMem"` + Steps uint `json:"exUnitsSteps"` +} + +type AlonzoGenesisExecutionPrices struct { + Steps *AlonzoGenesisExecutionPricesRat `json:"prSteps"` + Mem *AlonzoGenesisExecutionPricesRat `json:"prMem"` +} + +type AlonzoGenesisExecutionPricesRat struct { + *big.Rat +} + +func (r *AlonzoGenesisExecutionPricesRat) UnmarshalJSON(data []byte) error { + var tmpData struct { + Numerator int64 `json:"numerator"` + Denominator int64 `json:"denominator"` + } + if err := json.Unmarshal(data, &tmpData); err != nil { + return err + } + r.Rat = big.NewRat(tmpData.Numerator, tmpData.Denominator) + return nil +} diff --git a/ledger/alonzo/genesis_test.go b/ledger/alonzo/genesis_test.go index f804f18e..7069ba54 100644 --- a/ledger/alonzo/genesis_test.go +++ b/ledger/alonzo/genesis_test.go @@ -15,6 +15,7 @@ package alonzo_test import ( + "math/big" "reflect" "strings" "testing" @@ -226,17 +227,21 @@ var expectedGenesisObj = alonzo.AlonzoGenesis{ MaxValueSize: 5000, CollateralPercentage: 150, MaxCollateralInputs: 3, - ExecutionPrices: map[string]map[string]int{ - "prMem": {"denominator": 10000, "numerator": 577}, - "prSteps": {"denominator": 10000000, "numerator": 721}, + ExecutionPrices: alonzo.AlonzoGenesisExecutionPrices{ + Mem: &alonzo.AlonzoGenesisExecutionPricesRat{ + Rat: big.NewRat(577, 10000), + }, + Steps: &alonzo.AlonzoGenesisExecutionPricesRat{ + Rat: big.NewRat(721, 10000000), + }, }, - MaxTxExUnits: map[string]int{ - "exUnitsMem": 10000000, - "exUnitsSteps": 10000000000, + MaxTxExUnits: alonzo.AlonzoGenesisExUnits{ + Mem: 10000000, + Steps: 10000000000, }, - MaxBlockExUnits: map[string]int{ - "exUnitsMem": 50000000, - "exUnitsSteps": 40000000000, + MaxBlockExUnits: alonzo.AlonzoGenesisExUnits{ + Mem: 50000000, + Steps: 40000000000, }, CostModels: map[string]map[string]int{ "PlutusV1": { diff --git a/ledger/alonzo/pparams.go b/ledger/alonzo/pparams.go new file mode 100644 index 00000000..d28ae704 --- /dev/null +++ b/ledger/alonzo/pparams.go @@ -0,0 +1,109 @@ +// Copyright 2024 Blink Labs Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package alonzo + +import ( + "github.com/blinklabs-io/gouroboros/cbor" + "github.com/blinklabs-io/gouroboros/ledger/common" + "github.com/blinklabs-io/gouroboros/ledger/mary" +) + +type AlonzoProtocolParameters struct { + mary.MaryProtocolParameters + MinPoolCost uint64 + AdaPerUtxoByte uint64 + CostModels map[uint][]uint64 + ExecutionCosts common.ExUnitPrice + MaxTxExUnits common.ExUnit + MaxBlockExUnits common.ExUnit + MaxValueSize uint + CollateralPercentage uint + MaxCollateralInputs uint +} + +func (p *AlonzoProtocolParameters) Update(paramUpdate *AlonzoProtocolParameterUpdate) { + p.MaryProtocolParameters.Update( + ¶mUpdate.MaryProtocolParameterUpdate, + ) + if paramUpdate.MinPoolCost != nil { + p.MinPoolCost = *paramUpdate.MinPoolCost + } + if paramUpdate.AdaPerUtxoByte != nil { + p.AdaPerUtxoByte = *paramUpdate.AdaPerUtxoByte + } + if paramUpdate.CostModels != nil { + p.CostModels = paramUpdate.CostModels + } + if paramUpdate.ExecutionCosts != nil { + p.ExecutionCosts = *paramUpdate.ExecutionCosts + } + if paramUpdate.MaxTxExUnits != nil { + p.MaxTxExUnits = *paramUpdate.MaxTxExUnits + } + if paramUpdate.MaxBlockExUnits != nil { + p.MaxBlockExUnits = *paramUpdate.MaxBlockExUnits + } + if paramUpdate.MaxValueSize != nil { + p.MaxValueSize = *paramUpdate.MaxValueSize + } + if paramUpdate.CollateralPercentage != nil { + p.CollateralPercentage = *paramUpdate.CollateralPercentage + } + if paramUpdate.MaxCollateralInputs != nil { + p.MaxCollateralInputs = *paramUpdate.MaxCollateralInputs + } +} + +func (p *AlonzoProtocolParameters) UpdateFromGenesis(genesis *AlonzoGenesis) { + // XXX: do we need to convert this? + p.AdaPerUtxoByte = genesis.LovelacePerUtxoWord + p.MaxValueSize = genesis.MaxValueSize + p.CollateralPercentage = genesis.CollateralPercentage + p.MaxCollateralInputs = genesis.MaxCollateralInputs + p.MaxTxExUnits = common.ExUnit{ + Mem: genesis.MaxTxExUnits.Mem, + Steps: genesis.MaxTxExUnits.Steps, + } + p.MaxBlockExUnits = common.ExUnit{ + Mem: genesis.MaxBlockExUnits.Mem, + Steps: genesis.MaxBlockExUnits.Steps, + } + 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 + // We have 150+ string values to map to array indexes + // CostModels map[string]map[string]int +} + +type AlonzoProtocolParameterUpdate struct { + mary.MaryProtocolParameterUpdate + MinPoolCost *uint64 `cbor:"16,keyasint"` + AdaPerUtxoByte *uint64 `cbor:"17,keyasint"` + CostModels map[uint][]uint64 `cbor:"18,keyasint"` + ExecutionCosts *common.ExUnitPrice `cbor:"19,keyasint"` + MaxTxExUnits *common.ExUnit `cbor:"20,keyasint"` + MaxBlockExUnits *common.ExUnit `cbor:"21,keyasint"` + MaxValueSize *uint `cbor:"22,keyasint"` + CollateralPercentage *uint `cbor:"23,keyasint"` + MaxCollateralInputs *uint `cbor:"24,keyasint"` +} + +func (u *AlonzoProtocolParameterUpdate) UnmarshalCBOR(data []byte) error { + return u.UnmarshalCbor(data, u) +} diff --git a/ledger/alonzo/pparams_test.go b/ledger/alonzo/pparams_test.go new file mode 100644 index 00000000..762c0b1d --- /dev/null +++ b/ledger/alonzo/pparams_test.go @@ -0,0 +1,167 @@ +// Copyright 2024 Blink Labs Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package alonzo_test + +import ( + "encoding/hex" + "math/big" + "reflect" + "strings" + "testing" + + "github.com/blinklabs-io/gouroboros/cbor" + "github.com/blinklabs-io/gouroboros/ledger/allegra" + "github.com/blinklabs-io/gouroboros/ledger/alonzo" + "github.com/blinklabs-io/gouroboros/ledger/common" + "github.com/blinklabs-io/gouroboros/ledger/mary" + "github.com/blinklabs-io/gouroboros/ledger/shelley" +) + +func TestAlonzoProtocolParamsUpdate(t *testing.T) { + testDefs := []struct { + startParams alonzo.AlonzoProtocolParameters + updateCbor string + expectedParams alonzo.AlonzoProtocolParameters + }{ + { + startParams: alonzo.AlonzoProtocolParameters{ + MaryProtocolParameters: mary.MaryProtocolParameters{ + AllegraProtocolParameters: allegra.AllegraProtocolParameters{ + ShelleyProtocolParameters: shelley.ShelleyProtocolParameters{ + Decentralization: &cbor.Rat{Rat: new(big.Rat).SetInt64(1)}, + }, + }, + }, + }, + updateCbor: "a10cd81e82090a", + expectedParams: alonzo.AlonzoProtocolParameters{ + MaryProtocolParameters: mary.MaryProtocolParameters{ + AllegraProtocolParameters: allegra.AllegraProtocolParameters{ + ShelleyProtocolParameters: shelley.ShelleyProtocolParameters{ + Decentralization: &cbor.Rat{Rat: big.NewRat(9, 10)}, + }, + }, + }, + }, + }, + { + startParams: alonzo.AlonzoProtocolParameters{ + MaryProtocolParameters: mary.MaryProtocolParameters{ + AllegraProtocolParameters: allegra.AllegraProtocolParameters{ + ShelleyProtocolParameters: shelley.ShelleyProtocolParameters{ + ProtocolMajor: 5, + }, + }, + }, + }, + updateCbor: "a10e820600", + expectedParams: alonzo.AlonzoProtocolParameters{ + MaryProtocolParameters: mary.MaryProtocolParameters{ + AllegraProtocolParameters: allegra.AllegraProtocolParameters{ + ShelleyProtocolParameters: shelley.ShelleyProtocolParameters{ + ProtocolMajor: 6, + }, + }, + }, + }, + }, + { + startParams: alonzo.AlonzoProtocolParameters{ + MaryProtocolParameters: mary.MaryProtocolParameters{ + AllegraProtocolParameters: allegra.AllegraProtocolParameters{ + ShelleyProtocolParameters: shelley.ShelleyProtocolParameters{ + MaxBlockBodySize: 1, + }, + }, + }, + MaxTxExUnits: common.ExUnit{ + Mem: 1, + Steps: 1, + }, + }, + updateCbor: "a2021a0001200014821a00aba9501b00000002540be400", + expectedParams: alonzo.AlonzoProtocolParameters{ + MaryProtocolParameters: mary.MaryProtocolParameters{ + AllegraProtocolParameters: allegra.AllegraProtocolParameters{ + ShelleyProtocolParameters: shelley.ShelleyProtocolParameters{ + MaxBlockBodySize: 73728, + }, + }, + }, + MaxTxExUnits: common.ExUnit{ + Mem: 11250000, + Steps: 10000000000, + }, + }, + }, + } + for _, testDef := range testDefs { + cborBytes, err := hex.DecodeString(testDef.updateCbor) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + var tmpUpdate alonzo.AlonzoProtocolParameterUpdate + if _, err := cbor.Decode(cborBytes, &tmpUpdate); err != nil { + t.Fatalf("unexpected error: %s", err) + } + tmpParams := testDef.startParams + tmpParams.Update(&tmpUpdate) + if !reflect.DeepEqual(tmpParams, testDef.expectedParams) { + t.Fatalf("did not get expected params:\n got: %#v\n wanted: %#v", tmpParams, testDef.expectedParams) + } + } +} + +func TestShelleyProtocolParamsUpdateFromGenesis(t *testing.T) { + testDefs := []struct { + startParams alonzo.AlonzoProtocolParameters + genesisJson string + expectedParams alonzo.AlonzoProtocolParameters + }{ + { + startParams: alonzo.AlonzoProtocolParameters{ + MaryProtocolParameters: mary.MaryProtocolParameters{ + AllegraProtocolParameters: allegra.AllegraProtocolParameters{ + ShelleyProtocolParameters: shelley.ShelleyProtocolParameters{ + Decentralization: &cbor.Rat{Rat: new(big.Rat).SetInt64(1)}, + }, + }, + }, + }, + genesisJson: `{"lovelacePerUTxOWord": 34482}`, + expectedParams: alonzo.AlonzoProtocolParameters{ + MaryProtocolParameters: mary.MaryProtocolParameters{ + AllegraProtocolParameters: allegra.AllegraProtocolParameters{ + ShelleyProtocolParameters: shelley.ShelleyProtocolParameters{ + Decentralization: &cbor.Rat{Rat: new(big.Rat).SetInt64(1)}, + }, + }, + }, + AdaPerUtxoByte: 34482, + }, + }, + } + for _, testDef := range testDefs { + tmpGenesis, err := alonzo.NewAlonzoGenesisFromReader(strings.NewReader(testDef.genesisJson)) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + tmpParams := testDef.startParams + tmpParams.UpdateFromGenesis(&tmpGenesis) + if !reflect.DeepEqual(tmpParams, testDef.expectedParams) { + t.Fatalf("did not get expected params:\n got: %#v\n wanted: %#v", tmpParams, testDef.expectedParams) + } + } +} diff --git a/ledger/common/pparams.go b/ledger/common/pparams.go index 1b3abfd1..31e7580d 100644 --- a/ledger/common/pparams.go +++ b/ledger/common/pparams.go @@ -61,3 +61,15 @@ func (n *Nonce) UnmarshalCBOR(data []byte) error { } return nil } + +type ExUnit struct { + cbor.StructAsArray + Mem uint + Steps uint +} + +type ExUnitPrice struct { + cbor.StructAsArray + MemPrice *cbor.Rat + StepPrice *cbor.Rat +}