Skip to content

Commit

Permalink
feat: Shelley protocol param updates
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
agaffney committed Oct 3, 2024
1 parent a5a62f9 commit ebe50ca
Show file tree
Hide file tree
Showing 6 changed files with 349 additions and 147 deletions.
23 changes: 19 additions & 4 deletions ledger/shelley/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ package shelley

import (
"encoding/json"
"fmt"
"io"
"math/big"
"os"
"time"
)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
9 changes: 5 additions & 4 deletions ledger/shelley/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package shelley_test

import (
"math/big"
"reflect"
"strings"
"testing"
Expand Down Expand Up @@ -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",
},
Expand Down
194 changes: 194 additions & 0 deletions ledger/shelley/pparams.go
Original file line number Diff line number Diff line change
@@ -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{new(big.Rat).Set(genesisParams.A0.Rat)}

Check failure on line 109 in ledger/shelley/pparams.go

View workflow job for this annotation

GitHub Actions / lint

composites: github.com/blinklabs-io/gouroboros/cbor.Rat struct literal uses unkeyed fields (govet)
}
if genesisParams.Rho != nil {
p.Rho = &cbor.Rat{new(big.Rat).Set(genesisParams.Rho.Rat)}

Check failure on line 112 in ledger/shelley/pparams.go

View workflow job for this annotation

GitHub Actions / lint

composites: github.com/blinklabs-io/gouroboros/cbor.Rat struct literal uses unkeyed fields (govet)
}
if genesisParams.Tau != nil {
p.Tau = &cbor.Rat{new(big.Rat).Set(genesisParams.Tau.Rat)}

Check failure on line 115 in ledger/shelley/pparams.go

View workflow job for this annotation

GitHub Actions / lint

composites: github.com/blinklabs-io/gouroboros/cbor.Rat struct literal uses unkeyed fields (govet)
}
if genesisParams.Decentralization != nil {
p.Decentralization = &cbor.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
}
131 changes: 131 additions & 0 deletions ledger/shelley/pparams_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
Loading

0 comments on commit ebe50ca

Please sign in to comment.