From 9511e751da6875fe42409a2db2ff6fc3ce05b4f1 Mon Sep 17 00:00:00 2001 From: Aurora Gaffney Date: Tue, 1 Oct 2024 22:30:45 -0500 Subject: [PATCH] feat: Shelley protocol param updates * update protocol parameter set from protocol param update spec * update protocol parameter set from genesis config * create big.Rat wrapper for JSON unmarshal and use for genesis config decimal values --- ledger/shelley/genesis.go | 23 +++- ledger/shelley/genesis_test.go | 9 +- ledger/shelley/pparams.go | 194 +++++++++++++++++++++++++++++++++ ledger/shelley/pparams_test.go | 131 ++++++++++++++++++++++ ledger/shelley/shelley.go | 84 -------------- ledger/shelley/shelley_test.go | 55 ---------- 6 files changed, 349 insertions(+), 147 deletions(-) create mode 100644 ledger/shelley/pparams.go create mode 100644 ledger/shelley/pparams_test.go delete mode 100644 ledger/shelley/shelley_test.go diff --git a/ledger/shelley/genesis.go b/ledger/shelley/genesis.go index be61b320..92c8fd54 100644 --- a/ledger/shelley/genesis.go +++ b/ledger/shelley/genesis.go @@ -16,7 +16,9 @@ package shelley import ( "encoding/json" + "fmt" "io" + "math/big" "os" "time" ) @@ -49,10 +51,10 @@ type ShelleyGenesisProtocolParams struct { PoolDeposit uint MaxEpoch uint `json:"eMax"` NOpt uint - A0 float32 - Rho float32 - Tau float32 - Decentralization float32 `json:"decentralisationParam"` + A0 *GenesisRat + Rho *GenesisRat + Tau *GenesisRat + Decentralization *GenesisRat `json:"decentralisationParam"` ExtraEntropy map[string]string ProtocolVersion struct { Major uint @@ -80,3 +82,16 @@ func NewShelleyGenesisFromFile(path string) (ShelleyGenesis, error) { defer f.Close() return NewShelleyGenesisFromReader(f) } + +// GenesisRat is a wrapper to big.Rat that allows for unmarshaling from a bare float from JSON +type GenesisRat struct { + *big.Rat +} + +func (r *GenesisRat) UnmarshalJSON(data []byte) error { + r.Rat = new(big.Rat) + if _, ok := r.Rat.SetString(string(data)); !ok { + return fmt.Errorf("math/big: cannot unmarshal %q into a *big.Rat", data) + } + return nil +} diff --git a/ledger/shelley/genesis_test.go b/ledger/shelley/genesis_test.go index 382bf316..aac0c1c6 100644 --- a/ledger/shelley/genesis_test.go +++ b/ledger/shelley/genesis_test.go @@ -15,6 +15,7 @@ package shelley_test import ( + "math/big" "reflect" "strings" "testing" @@ -125,10 +126,10 @@ var expectedGenesisObj = shelley.ShelleyGenesis{ PoolDeposit: 500000000, MaxEpoch: 18, NOpt: 150, - A0: 0.3, - Rho: 0.003, - Tau: 0.2, - Decentralization: 1, + A0: &shelley.GenesisRat{Rat: big.NewRat(3, 10)}, + Rho: &shelley.GenesisRat{Rat: big.NewRat(3, 1000)}, + Tau: &shelley.GenesisRat{Rat: big.NewRat(2, 10)}, + Decentralization: &shelley.GenesisRat{Rat: new(big.Rat).SetInt64(1)}, ExtraEntropy: map[string]string{ "tag": "NeutralNonce", }, diff --git a/ledger/shelley/pparams.go b/ledger/shelley/pparams.go new file mode 100644 index 00000000..3dc9e0cc --- /dev/null +++ b/ledger/shelley/pparams.go @@ -0,0 +1,194 @@ +// 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 shelley + +import ( + "fmt" + "math/big" + + "github.com/blinklabs-io/gouroboros/cbor" +) + +type ShelleyProtocolParameters struct { + cbor.StructAsArray + MinFeeA uint + MinFeeB uint + MaxBlockBodySize uint + MaxTxSize uint + MaxBlockHeaderSize uint + KeyDeposit uint + PoolDeposit uint + MaxEpoch uint + NOpt uint + A0 *cbor.Rat + Rho *cbor.Rat + Tau *cbor.Rat + Decentralization *cbor.Rat + Nonce *Nonce + ProtocolMajor uint + ProtocolMinor uint + MinUtxoValue uint +} + +func (p *ShelleyProtocolParameters) Update(paramUpdate *ShelleyProtocolParameterUpdate) { + if paramUpdate.MinFeeA != nil { + p.MinFeeA = *paramUpdate.MinFeeA + } + if paramUpdate.MinFeeB != nil { + p.MinFeeB = *paramUpdate.MinFeeB + } + if paramUpdate.MaxBlockBodySize != nil { + p.MaxBlockBodySize = *paramUpdate.MaxBlockBodySize + } + if paramUpdate.MaxTxSize != nil { + p.MaxTxSize = *paramUpdate.MaxTxSize + } + if paramUpdate.MaxBlockHeaderSize != nil { + p.MaxBlockHeaderSize = *paramUpdate.MaxBlockHeaderSize + } + if paramUpdate.KeyDeposit != nil { + p.KeyDeposit = *paramUpdate.KeyDeposit + } + if paramUpdate.PoolDeposit != nil { + p.PoolDeposit = *paramUpdate.PoolDeposit + } + if paramUpdate.MaxEpoch != nil { + p.MaxEpoch = *paramUpdate.MaxEpoch + } + if paramUpdate.NOpt != nil { + p.NOpt = *paramUpdate.NOpt + } + if paramUpdate.A0 != nil { + p.A0 = paramUpdate.A0 + } + if paramUpdate.Rho != nil { + p.Rho = paramUpdate.Rho + } + if paramUpdate.Tau != nil { + p.Tau = paramUpdate.Tau + } + if paramUpdate.Decentralization != nil { + p.Decentralization = paramUpdate.Decentralization + } + if paramUpdate.ProtocolVersion != nil { + p.ProtocolMajor = paramUpdate.ProtocolVersion.Major + p.ProtocolMinor = paramUpdate.ProtocolVersion.Minor + } + if paramUpdate.Nonce != nil { + p.Nonce = paramUpdate.Nonce + } + if paramUpdate.MinUtxoValue != nil { + p.MinUtxoValue = *paramUpdate.MinUtxoValue + } +} + +func (p *ShelleyProtocolParameters) UpdateFromGenesis(genesis *ShelleyGenesis) { + genesisParams := genesis.ProtocolParameters + p.MinFeeA = genesisParams.MinFeeA + p.MinFeeB = genesisParams.MinFeeB + p.MaxBlockBodySize = genesisParams.MaxBlockBodySize + p.MaxTxSize = genesisParams.MaxTxSize + p.MaxBlockHeaderSize = genesisParams.MaxBlockHeaderSize + p.KeyDeposit = genesisParams.KeyDeposit + p.PoolDeposit = genesisParams.PoolDeposit + p.MaxEpoch = genesisParams.MaxEpoch + p.NOpt = genesisParams.NOpt + if genesisParams.A0 != nil { + p.A0 = &cbor.Rat{Rat: new(big.Rat).Set(genesisParams.A0.Rat)} + } + if genesisParams.Rho != nil { + p.Rho = &cbor.Rat{Rat: new(big.Rat).Set(genesisParams.Rho.Rat)} + } + if genesisParams.Tau != nil { + p.Tau = &cbor.Rat{Rat: new(big.Rat).Set(genesisParams.Tau.Rat)} + } + if genesisParams.Decentralization != nil { + p.Decentralization = &cbor.Rat{Rat: new(big.Rat).Set(genesisParams.Decentralization.Rat)} + } + p.ProtocolMajor = genesisParams.ProtocolVersion.Major + p.ProtocolMinor = genesisParams.ProtocolVersion.Minor + p.MinUtxoValue = genesisParams.MinUtxoValue + // TODO: + //p.Nonce *cbor.Rat +} + +type ShelleyProtocolParametersProtocolVersion struct { + cbor.StructAsArray + Major uint + Minor uint +} + +type ShelleyProtocolParameterUpdate struct { + cbor.DecodeStoreCbor + MinFeeA *uint `cbor:"0,keyasint"` + MinFeeB *uint `cbor:"1,keyasint"` + MaxBlockBodySize *uint `cbor:"2,keyasint"` + MaxTxSize *uint `cbor:"3,keyasint"` + MaxBlockHeaderSize *uint `cbor:"4,keyasint"` + KeyDeposit *uint `cbor:"5,keyasint"` + PoolDeposit *uint `cbor:"6,keyasint"` + MaxEpoch *uint `cbor:"7,keyasint"` + NOpt *uint `cbor:"8,keyasint"` + A0 *cbor.Rat `cbor:"9,keyasint"` + Rho *cbor.Rat `cbor:"10,keyasint"` + Tau *cbor.Rat `cbor:"11,keyasint"` + Decentralization *cbor.Rat `cbor:"12,keyasint"` + Nonce *Nonce `cbor:"13,keyasint"` + ProtocolVersion *ShelleyProtocolParametersProtocolVersion `cbor:"14,keyasint"` + MinUtxoValue *uint `cbor:"15,keyasint"` +} + +func (ShelleyProtocolParameterUpdate) IsProtocolParameterUpdate() {} + +func (u *ShelleyProtocolParameterUpdate) UnmarshalCBOR(data []byte) error { + return u.UnmarshalCbor(data, u) +} + +const ( + NonceType0 = 0 + NonceType1 = 1 +) + +var NeutralNonce = Nonce{ + Type: NonceType0, +} + +type Nonce struct { + cbor.StructAsArray + Type uint + Value [32]byte +} + +func (n *Nonce) UnmarshalCBOR(data []byte) error { + nonceType, err := cbor.DecodeIdFromList(data) + if err != nil { + return err + } + + n.Type = uint(nonceType) + + switch nonceType { + case NonceType0: + // Value uses default value + case NonceType1: + if err := cbor.DecodeGeneric(data, n); err != nil { + fmt.Printf("Nonce decode error: %+v\n", data) + return err + } + default: + return fmt.Errorf("unsupported nonce type %d", nonceType) + } + return nil +} diff --git a/ledger/shelley/pparams_test.go b/ledger/shelley/pparams_test.go new file mode 100644 index 00000000..c0fba6ed --- /dev/null +++ b/ledger/shelley/pparams_test.go @@ -0,0 +1,131 @@ +// 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 shelley_test + +import ( + "encoding/hex" + "math/big" + "reflect" + "strings" + "testing" + + "github.com/blinklabs-io/gouroboros/cbor" + "github.com/blinklabs-io/gouroboros/ledger/shelley" +) + +func TestNonceUnmarshalCBOR(t *testing.T) { + testCases := []struct { + name string + data []byte + expectedErr string + }{ + { + name: "NonceType0", + data: []byte{0x81, 0x00}, + }, + { + name: "NonceType1", + data: []byte{0x82, 0x01, 0x42, 0x01, 0x02}, + }, + { + name: "UnsupportedNonceType", + data: []byte{0x82, 0x02}, + expectedErr: "unsupported nonce type 2", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + n := &shelley.Nonce{} + err := n.UnmarshalCBOR(tc.data) + if err != nil { + if tc.expectedErr == "" || err.Error() != tc.expectedErr { + t.Errorf("unexpected error: %v", err) + } + } else if tc.expectedErr != "" { + t.Errorf("expected error: %v, got nil", tc.expectedErr) + } + }) + } +} + +func TestShelleyProtocolParamsUpdate(t *testing.T) { + testDefs := []struct { + startParams shelley.ShelleyProtocolParameters + updateCbor string + expectedParams shelley.ShelleyProtocolParameters + }{ + { + startParams: shelley.ShelleyProtocolParameters{ + Decentralization: &cbor.Rat{new(big.Rat).SetInt64(1)}, + }, + updateCbor: "a10cd81e82090a", + expectedParams: shelley.ShelleyProtocolParameters{ + Decentralization: &cbor.Rat{big.NewRat(9, 10)}, + }, + }, + { + startParams: shelley.ShelleyProtocolParameters{ + ProtocolMajor: 2, + }, + updateCbor: "a10e820300", + expectedParams: shelley.ShelleyProtocolParameters{ + ProtocolMajor: 3, + }, + }, + } + for _, testDef := range testDefs { + cborBytes, err := hex.DecodeString(testDef.updateCbor) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + var tmpUpdate shelley.ShelleyProtocolParameterUpdate + 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 shelley.ShelleyProtocolParameters + genesisJson string + expectedParams shelley.ShelleyProtocolParameters + }{ + { + startParams: shelley.ShelleyProtocolParameters{}, + genesisJson: `{"protocolParams":{"decentralisationParam":0.9}}`, + expectedParams: shelley.ShelleyProtocolParameters{ + Decentralization: &cbor.Rat{big.NewRat(9, 10)}, + }, + }, + } + for _, testDef := range testDefs { + tmpGenesis, err := shelley.NewShelleyGenesisFromReader(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/shelley/shelley.go b/ledger/shelley/shelley.go index f96f88df..226dafe4 100644 --- a/ledger/shelley/shelley.go +++ b/ledger/shelley/shelley.go @@ -584,90 +584,6 @@ func (t *ShelleyTransaction) Cbor() []byte { return cborData } -type ShelleyProtocolParameters struct { - cbor.StructAsArray - MinFeeA uint - MinFeeB uint - MaxBlockBodySize uint - MaxTxSize uint - MaxBlockHeaderSize uint - KeyDeposit uint - PoolDeposit uint - MaxEpoch uint - NOpt uint - A0 *cbor.Rat - Rho *cbor.Rat - Tau *cbor.Rat - Decentralization *cbor.Rat - Nonce *cbor.Rat - ProtocolMajor uint - ProtocolMinor uint - MinUtxoValue uint -} - -type ShelleyProtocolParameterUpdate struct { - cbor.DecodeStoreCbor - MinFeeA uint `cbor:"0,keyasint"` - MinFeeB uint `cbor:"1,keyasint"` - MaxBlockBodySize uint `cbor:"2,keyasint"` - MaxTxSize uint `cbor:"3,keyasint"` - MaxBlockHeaderSize uint `cbor:"4,keyasint"` - KeyDeposit uint `cbor:"5,keyasint"` - PoolDeposit uint `cbor:"6,keyasint"` - MaxEpoch uint `cbor:"7,keyasint"` - NOpt uint `cbor:"8,keyasint"` - A0 *cbor.Rat `cbor:"9,keyasint"` - Rho *cbor.Rat `cbor:"10,keyasint"` - Tau *cbor.Rat `cbor:"11,keyasint"` - Decentralization *cbor.Rat `cbor:"12,keyasint"` - Nonce *Nonce `cbor:"13,keyasint"` - ProtocolVersion struct { - cbor.StructAsArray - Major uint - Minor uint - } `cbor:"14,keyasint"` - MinUtxoValue uint `cbor:"15,keyasint"` -} - -func (ShelleyProtocolParameterUpdate) IsProtocolParameterUpdate() {} - -func (u *ShelleyProtocolParameterUpdate) UnmarshalCBOR(data []byte) error { - return u.UnmarshalCbor(data, u) -} - -const ( - NonceType0 = 0 - NonceType1 = 1 -) - -type Nonce struct { - cbor.StructAsArray - Type uint - Value [32]byte -} - -func (n *Nonce) UnmarshalCBOR(data []byte) error { - nonceType, err := cbor.DecodeIdFromList(data) - if err != nil { - return err - } - - n.Type = uint(nonceType) - - switch nonceType { - case NonceType0: - // Value uses default value - case NonceType1: - if err := cbor.DecodeGeneric(data, n); err != nil { - fmt.Printf("Nonce decode error: %+v\n", data) - return err - } - default: - return fmt.Errorf("unsupported nonce type %d", nonceType) - } - return nil -} - func NewShelleyBlockFromCbor(data []byte) (*ShelleyBlock, error) { var shelleyBlock ShelleyBlock if _, err := cbor.Decode(data, &shelleyBlock); err != nil { diff --git a/ledger/shelley/shelley_test.go b/ledger/shelley/shelley_test.go deleted file mode 100644 index 40f0749d..00000000 --- a/ledger/shelley/shelley_test.go +++ /dev/null @@ -1,55 +0,0 @@ -// 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 shelley - -import ( - "testing" -) - -func TestNonceUnmarshalCBOR(t *testing.T) { - testCases := []struct { - name string - data []byte - expectedErr string - }{ - { - name: "NonceType0", - data: []byte{0x81, 0x00}, - }, - { - name: "NonceType1", - data: []byte{0x82, 0x01, 0x42, 0x01, 0x02}, - }, - { - name: "UnsupportedNonceType", - data: []byte{0x82, 0x02}, - expectedErr: "unsupported nonce type 2", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - n := &Nonce{} - err := n.UnmarshalCBOR(tc.data) - if err != nil { - if tc.expectedErr == "" || err.Error() != tc.expectedErr { - t.Errorf("unexpected error: %v", err) - } - } else if tc.expectedErr != "" { - t.Errorf("expected error: %v, got nil", tc.expectedErr) - } - }) - } -}