Skip to content

Commit b6619fd

Browse files
committed
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
1 parent a5a62f9 commit b6619fd

File tree

6 files changed

+349
-147
lines changed

6 files changed

+349
-147
lines changed

ledger/shelley/genesis.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ package shelley
1616

1717
import (
1818
"encoding/json"
19+
"fmt"
1920
"io"
21+
"math/big"
2022
"os"
2123
"time"
2224
)
@@ -49,10 +51,10 @@ type ShelleyGenesisProtocolParams struct {
4951
PoolDeposit uint
5052
MaxEpoch uint `json:"eMax"`
5153
NOpt uint
52-
A0 float32
53-
Rho float32
54-
Tau float32
55-
Decentralization float32 `json:"decentralisationParam"`
54+
A0 *GenesisRat
55+
Rho *GenesisRat
56+
Tau *GenesisRat
57+
Decentralization *GenesisRat `json:"decentralisationParam"`
5658
ExtraEntropy map[string]string
5759
ProtocolVersion struct {
5860
Major uint
@@ -80,3 +82,16 @@ func NewShelleyGenesisFromFile(path string) (ShelleyGenesis, error) {
8082
defer f.Close()
8183
return NewShelleyGenesisFromReader(f)
8284
}
85+
86+
// GenesisRat is a wrapper to big.Rat that allows for unmarshaling from a bare float from JSON
87+
type GenesisRat struct {
88+
*big.Rat
89+
}
90+
91+
func (r *GenesisRat) UnmarshalJSON(data []byte) error {
92+
r.Rat = new(big.Rat)
93+
if _, ok := r.Rat.SetString(string(data)); !ok {
94+
return fmt.Errorf("math/big: cannot unmarshal %q into a *big.Rat", data)
95+
}
96+
return nil
97+
}

ledger/shelley/genesis_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package shelley_test
1616

1717
import (
18+
"math/big"
1819
"reflect"
1920
"strings"
2021
"testing"
@@ -125,10 +126,10 @@ var expectedGenesisObj = shelley.ShelleyGenesis{
125126
PoolDeposit: 500000000,
126127
MaxEpoch: 18,
127128
NOpt: 150,
128-
A0: 0.3,
129-
Rho: 0.003,
130-
Tau: 0.2,
131-
Decentralization: 1,
129+
A0: &shelley.GenesisRat{Rat: big.NewRat(3, 10)},
130+
Rho: &shelley.GenesisRat{Rat: big.NewRat(3, 1000)},
131+
Tau: &shelley.GenesisRat{Rat: big.NewRat(2, 10)},
132+
Decentralization: &shelley.GenesisRat{Rat: new(big.Rat).SetInt64(1)},
132133
ExtraEntropy: map[string]string{
133134
"tag": "NeutralNonce",
134135
},

ledger/shelley/pparams.go

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
// Copyright 2024 Blink Labs Software
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package shelley
16+
17+
import (
18+
"fmt"
19+
"math/big"
20+
21+
"github.com/blinklabs-io/gouroboros/cbor"
22+
)
23+
24+
type ShelleyProtocolParameters struct {
25+
cbor.StructAsArray
26+
MinFeeA uint
27+
MinFeeB uint
28+
MaxBlockBodySize uint
29+
MaxTxSize uint
30+
MaxBlockHeaderSize uint
31+
KeyDeposit uint
32+
PoolDeposit uint
33+
MaxEpoch uint
34+
NOpt uint
35+
A0 *cbor.Rat
36+
Rho *cbor.Rat
37+
Tau *cbor.Rat
38+
Decentralization *cbor.Rat
39+
Nonce *Nonce
40+
ProtocolMajor uint
41+
ProtocolMinor uint
42+
MinUtxoValue uint
43+
}
44+
45+
func (p *ShelleyProtocolParameters) Update(paramUpdate *ShelleyProtocolParameterUpdate) {
46+
if paramUpdate.MinFeeA != nil {
47+
p.MinFeeA = *paramUpdate.MinFeeA
48+
}
49+
if paramUpdate.MinFeeB != nil {
50+
p.MinFeeB = *paramUpdate.MinFeeB
51+
}
52+
if paramUpdate.MaxBlockBodySize != nil {
53+
p.MaxBlockBodySize = *paramUpdate.MaxBlockBodySize
54+
}
55+
if paramUpdate.MaxTxSize != nil {
56+
p.MaxTxSize = *paramUpdate.MaxTxSize
57+
}
58+
if paramUpdate.MaxBlockHeaderSize != nil {
59+
p.MaxBlockHeaderSize = *paramUpdate.MaxBlockHeaderSize
60+
}
61+
if paramUpdate.KeyDeposit != nil {
62+
p.KeyDeposit = *paramUpdate.KeyDeposit
63+
}
64+
if paramUpdate.PoolDeposit != nil {
65+
p.PoolDeposit = *paramUpdate.PoolDeposit
66+
}
67+
if paramUpdate.MaxEpoch != nil {
68+
p.MaxEpoch = *paramUpdate.MaxEpoch
69+
}
70+
if paramUpdate.NOpt != nil {
71+
p.NOpt = *paramUpdate.NOpt
72+
}
73+
if paramUpdate.A0 != nil {
74+
p.A0 = paramUpdate.A0
75+
}
76+
if paramUpdate.Rho != nil {
77+
p.Rho = paramUpdate.Rho
78+
}
79+
if paramUpdate.Tau != nil {
80+
p.Tau = paramUpdate.Tau
81+
}
82+
if paramUpdate.Decentralization != nil {
83+
p.Decentralization = paramUpdate.Decentralization
84+
}
85+
if paramUpdate.ProtocolVersion != nil {
86+
p.ProtocolMajor = paramUpdate.ProtocolVersion.Major
87+
p.ProtocolMinor = paramUpdate.ProtocolVersion.Minor
88+
}
89+
if paramUpdate.Nonce != nil {
90+
p.Nonce = paramUpdate.Nonce
91+
}
92+
if paramUpdate.MinUtxoValue != nil {
93+
p.MinUtxoValue = *paramUpdate.MinUtxoValue
94+
}
95+
}
96+
97+
func (p *ShelleyProtocolParameters) UpdateFromGenesis(genesis *ShelleyGenesis) {
98+
genesisParams := genesis.ProtocolParameters
99+
p.MinFeeA = genesisParams.MinFeeA
100+
p.MinFeeB = genesisParams.MinFeeB
101+
p.MaxBlockBodySize = genesisParams.MaxBlockBodySize
102+
p.MaxTxSize = genesisParams.MaxTxSize
103+
p.MaxBlockHeaderSize = genesisParams.MaxBlockHeaderSize
104+
p.KeyDeposit = genesisParams.KeyDeposit
105+
p.PoolDeposit = genesisParams.PoolDeposit
106+
p.MaxEpoch = genesisParams.MaxEpoch
107+
p.NOpt = genesisParams.NOpt
108+
if genesisParams.A0 != nil {
109+
p.A0 = &cbor.Rat{Rat: new(big.Rat).Set(genesisParams.A0.Rat)}
110+
}
111+
if genesisParams.Rho != nil {
112+
p.Rho = &cbor.Rat{Rat: new(big.Rat).Set(genesisParams.Rho.Rat)}
113+
}
114+
if genesisParams.Tau != nil {
115+
p.Tau = &cbor.Rat{Rat: new(big.Rat).Set(genesisParams.Tau.Rat)}
116+
}
117+
if genesisParams.Decentralization != nil {
118+
p.Decentralization = &cbor.Rat{Rat: new(big.Rat).Set(genesisParams.Decentralization.Rat)}
119+
}
120+
p.ProtocolMajor = genesisParams.ProtocolVersion.Major
121+
p.ProtocolMinor = genesisParams.ProtocolVersion.Minor
122+
p.MinUtxoValue = genesisParams.MinUtxoValue
123+
// TODO:
124+
//p.Nonce *cbor.Rat
125+
}
126+
127+
type ShelleyProtocolParametersProtocolVersion struct {
128+
cbor.StructAsArray
129+
Major uint
130+
Minor uint
131+
}
132+
133+
type ShelleyProtocolParameterUpdate struct {
134+
cbor.DecodeStoreCbor
135+
MinFeeA *uint `cbor:"0,keyasint"`
136+
MinFeeB *uint `cbor:"1,keyasint"`
137+
MaxBlockBodySize *uint `cbor:"2,keyasint"`
138+
MaxTxSize *uint `cbor:"3,keyasint"`
139+
MaxBlockHeaderSize *uint `cbor:"4,keyasint"`
140+
KeyDeposit *uint `cbor:"5,keyasint"`
141+
PoolDeposit *uint `cbor:"6,keyasint"`
142+
MaxEpoch *uint `cbor:"7,keyasint"`
143+
NOpt *uint `cbor:"8,keyasint"`
144+
A0 *cbor.Rat `cbor:"9,keyasint"`
145+
Rho *cbor.Rat `cbor:"10,keyasint"`
146+
Tau *cbor.Rat `cbor:"11,keyasint"`
147+
Decentralization *cbor.Rat `cbor:"12,keyasint"`
148+
Nonce *Nonce `cbor:"13,keyasint"`
149+
ProtocolVersion *ShelleyProtocolParametersProtocolVersion `cbor:"14,keyasint"`
150+
MinUtxoValue *uint `cbor:"15,keyasint"`
151+
}
152+
153+
func (ShelleyProtocolParameterUpdate) IsProtocolParameterUpdate() {}
154+
155+
func (u *ShelleyProtocolParameterUpdate) UnmarshalCBOR(data []byte) error {
156+
return u.UnmarshalCbor(data, u)
157+
}
158+
159+
const (
160+
NonceType0 = 0
161+
NonceType1 = 1
162+
)
163+
164+
var NeutralNonce = Nonce{
165+
Type: NonceType0,
166+
}
167+
168+
type Nonce struct {
169+
cbor.StructAsArray
170+
Type uint
171+
Value [32]byte
172+
}
173+
174+
func (n *Nonce) UnmarshalCBOR(data []byte) error {
175+
nonceType, err := cbor.DecodeIdFromList(data)
176+
if err != nil {
177+
return err
178+
}
179+
180+
n.Type = uint(nonceType)
181+
182+
switch nonceType {
183+
case NonceType0:
184+
// Value uses default value
185+
case NonceType1:
186+
if err := cbor.DecodeGeneric(data, n); err != nil {
187+
fmt.Printf("Nonce decode error: %+v\n", data)
188+
return err
189+
}
190+
default:
191+
return fmt.Errorf("unsupported nonce type %d", nonceType)
192+
}
193+
return nil
194+
}

ledger/shelley/pparams_test.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright 2024 Blink Labs Software
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package shelley_test
16+
17+
import (
18+
"encoding/hex"
19+
"math/big"
20+
"reflect"
21+
"strings"
22+
"testing"
23+
24+
"github.com/blinklabs-io/gouroboros/cbor"
25+
"github.com/blinklabs-io/gouroboros/ledger/shelley"
26+
)
27+
28+
func TestNonceUnmarshalCBOR(t *testing.T) {
29+
testCases := []struct {
30+
name string
31+
data []byte
32+
expectedErr string
33+
}{
34+
{
35+
name: "NonceType0",
36+
data: []byte{0x81, 0x00},
37+
},
38+
{
39+
name: "NonceType1",
40+
data: []byte{0x82, 0x01, 0x42, 0x01, 0x02},
41+
},
42+
{
43+
name: "UnsupportedNonceType",
44+
data: []byte{0x82, 0x02},
45+
expectedErr: "unsupported nonce type 2",
46+
},
47+
}
48+
49+
for _, tc := range testCases {
50+
t.Run(tc.name, func(t *testing.T) {
51+
n := &shelley.Nonce{}
52+
err := n.UnmarshalCBOR(tc.data)
53+
if err != nil {
54+
if tc.expectedErr == "" || err.Error() != tc.expectedErr {
55+
t.Errorf("unexpected error: %v", err)
56+
}
57+
} else if tc.expectedErr != "" {
58+
t.Errorf("expected error: %v, got nil", tc.expectedErr)
59+
}
60+
})
61+
}
62+
}
63+
64+
func TestShelleyProtocolParamsUpdate(t *testing.T) {
65+
testDefs := []struct {
66+
startParams shelley.ShelleyProtocolParameters
67+
updateCbor string
68+
expectedParams shelley.ShelleyProtocolParameters
69+
}{
70+
{
71+
startParams: shelley.ShelleyProtocolParameters{
72+
Decentralization: &cbor.Rat{Rat: new(big.Rat).SetInt64(1)},
73+
},
74+
updateCbor: "a10cd81e82090a",
75+
expectedParams: shelley.ShelleyProtocolParameters{
76+
Decentralization: &cbor.Rat{Rat: big.NewRat(9, 10)},
77+
},
78+
},
79+
{
80+
startParams: shelley.ShelleyProtocolParameters{
81+
ProtocolMajor: 2,
82+
},
83+
updateCbor: "a10e820300",
84+
expectedParams: shelley.ShelleyProtocolParameters{
85+
ProtocolMajor: 3,
86+
},
87+
},
88+
}
89+
for _, testDef := range testDefs {
90+
cborBytes, err := hex.DecodeString(testDef.updateCbor)
91+
if err != nil {
92+
t.Fatalf("unexpected error: %s", err)
93+
}
94+
var tmpUpdate shelley.ShelleyProtocolParameterUpdate
95+
if _, err := cbor.Decode(cborBytes, &tmpUpdate); err != nil {
96+
t.Fatalf("unexpected error: %s", err)
97+
}
98+
tmpParams := testDef.startParams
99+
tmpParams.Update(&tmpUpdate)
100+
if !reflect.DeepEqual(tmpParams, testDef.expectedParams) {
101+
t.Fatalf("did not get expected params:\n got: %#v\n wanted: %#v", tmpParams, testDef.expectedParams)
102+
}
103+
}
104+
}
105+
106+
func TestShelleyProtocolParamsUpdateFromGenesis(t *testing.T) {
107+
testDefs := []struct {
108+
startParams shelley.ShelleyProtocolParameters
109+
genesisJson string
110+
expectedParams shelley.ShelleyProtocolParameters
111+
}{
112+
{
113+
startParams: shelley.ShelleyProtocolParameters{},
114+
genesisJson: `{"protocolParams":{"decentralisationParam":0.9}}`,
115+
expectedParams: shelley.ShelleyProtocolParameters{
116+
Decentralization: &cbor.Rat{Rat: big.NewRat(9, 10)},
117+
},
118+
},
119+
}
120+
for _, testDef := range testDefs {
121+
tmpGenesis, err := shelley.NewShelleyGenesisFromReader(strings.NewReader(testDef.genesisJson))
122+
if err != nil {
123+
t.Fatalf("unexpected error: %s", err)
124+
}
125+
tmpParams := testDef.startParams
126+
tmpParams.UpdateFromGenesis(&tmpGenesis)
127+
if !reflect.DeepEqual(tmpParams, testDef.expectedParams) {
128+
t.Fatalf("did not get expected params:\n got: %#v\n wanted: %#v", tmpParams, testDef.expectedParams)
129+
}
130+
}
131+
}

0 commit comments

Comments
 (0)