From 2adef07a61b2d294820193dbd23b97427bd9e1fe Mon Sep 17 00:00:00 2001 From: Akhil Repala Date: Mon, 15 Sep 2025 00:45:19 -0500 Subject: [PATCH 1/3] fix(ledger/byron): Added changes to support ByronGenessis ftsSeed as string or empty object Signed-off-by: Akhil Repala --- ledger/byron/genesis.go | 57 ++++++++++++++++++++++++++++- ledger/byron/genesis_test.go | 69 ++++++++++++++++++++++++++++++++++-- 2 files changed, 123 insertions(+), 3 deletions(-) diff --git a/ledger/byron/genesis.go b/ledger/byron/genesis.go index 3c0e59d6..8ebcd245 100644 --- a/ledger/byron/genesis.go +++ b/ledger/byron/genesis.go @@ -17,6 +17,7 @@ package byron import ( "encoding/base64" "encoding/json" + "errors" "io" "os" "slices" @@ -25,10 +26,14 @@ import ( "github.com/blinklabs-io/gouroboros/ledger/common" ) +type ByronFtsSeed struct { + Value string + IsObject bool +} type ByronGenesis struct { AvvmDistr map[string]string `json:"avvmDistr"` BlockVersionData ByronGenesisBlockVersionData `json:"blockVersionData"` - FtsSeed string `json:"ftsSeed"` + FtsSeed ByronFtsSeed `json:"ftsSeed"` ProtocolConsts ByronGenesisProtocolConsts `json:"protocolConsts"` StartTime int `json:"startTime"` BootStakeholders map[string]int `json:"bootStakeholders"` @@ -193,3 +198,53 @@ func NewByronGenesisFromFile(path string) (ByronGenesis, error) { defer f.Close() return NewByronGenesisFromReader(f) } + +// UnmarshalJSON accepts: "string", {}, or null +func (f *ByronFtsSeed) UnmarshalJSON(b []byte) error { + var first byte + for _, c := range b { + if c > ' ' { + first = c + break + } + } + switch first { + case '"': + // Normal string case + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + f.Value = s + f.IsObject = false + return nil + case '{': + // empty object case + var m map[string]any + if err := json.Unmarshal(b, &m); err != nil { + return err + } + if len(m) > 0 { + return errors.New("ftsSeed: expected empty object or string") + } + f.Value = "" + f.IsObject = true + return nil + case 'n': + return nil + default: + return errors.New("ftsSeed: expected string, empty object, or null") + } +} + +func (f ByronFtsSeed) MarshalJSON() ([]byte, error) { + if f.IsObject { + // serialize as empty object + return []byte(`{}`), nil + } + if f.Value == "" { + // serialize as null + return []byte(`null`), nil + } + return json.Marshal(f.Value) +} diff --git a/ledger/byron/genesis_test.go b/ledger/byron/genesis_test.go index ea393e7b..a0055e33 100644 --- a/ledger/byron/genesis_test.go +++ b/ledger/byron/genesis_test.go @@ -55,7 +55,7 @@ const byronGenesisConfig = ` "updateProposalThd": "100000000000000", "updateVoteThd": "1000000000000" }, - "ftsSeed": "76617361206f7061736120736b6f766f726f64612047677572646120626f726f64612070726f766f6461", + "ftsSeed": "76617361206f7061736120736b6f766f726f64612047677572646120626f726f64612070726f766f6461", "protocolConsts": { "k": 2160, "protocolMagic": 764824073, @@ -128,7 +128,10 @@ var expectedGenesisObj = byron.ByronGenesis{ UpdateProposalThd: 100000000000000, UpdateVoteThd: 1000000000000, }, - FtsSeed: "76617361206f7061736120736b6f766f726f64612047677572646120626f726f64612070726f766f6461", + FtsSeed: byron.ByronFtsSeed{ + Value: "76617361206f7061736120736b6f766f726f64612047677572646120626f726f64612070726f766f6461", + IsObject: false, + }, ProtocolConsts: byron.ByronGenesisProtocolConsts{ K: 2160, ProtocolMagic: 764824073, @@ -431,3 +434,65 @@ func TestNewByronGenesisFromReader(t *testing.T) { ) } } + +func TestGenesis_FtsSeed_EmptyObject(t *testing.T) { + jsonData := `{ + "avvmDistr": { "addr1": "1000" }, + "blockVersionData": { + "heavyDelThd": "1", + "maxBlockSize": "2", + "maxHeaderSize": "3", + "maxProposalSize": "4", + "maxTxSize": "5", + "mpcThd": "6", + "scriptVersion": 1, + "slotDuration": "7", + "softforkRule": { + "initThd": "8", + "minThd": "9", + "thdDecrement": "10" + }, + "txFeePolicy": { + "multiplier": "11", + "summand": "12" + }, + "unlockStakeEpoch": "13", + "updateImplicit": "14", + "updateProposalThd": "15", + "updateVoteThd": "16" + }, + "ftsSeed": {}, + "protocolConsts": { + "k": 1, + "protocolMagic": 42, + "vssMinTtl": 2, + "vssMaxTtl": 10 + }, + "startTime": 100000, + "bootStakeholders": { "stakeholder1": 1 }, + "heavyDelegation": { + "key1": { + "cert": "cert-val", + "delegatePk": "delegate-pk", + "issuerPk": "issuer-pk", + "omega": 5 + } + }, + "nonAvvmBalances": { "addr2": "2000" }, + "vssCerts": { + "cert1": { + "expiryEpoch": 5, + "signature": "sig", + "signingKey": "sign-key", + "vssKey": "vss-key" + } + } + }` + got, err := byron.NewByronGenesisFromReader(strings.NewReader(jsonData)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got.FtsSeed.Value != "" || !got.FtsSeed.IsObject { + t.Fatalf("ftsSeed not parsed as empty object; got=%#v", got.FtsSeed) + } +} From aabfe4cec4a8251ce93858ddcff2e4e6b26412e9 Mon Sep 17 00:00:00 2001 From: Akhil Repala Date: Mon, 15 Sep 2025 00:48:29 -0500 Subject: [PATCH 2/3] fix(ledger/byron): Renamed ByronFtsSeed to ByronGenesisFtsSeed Signed-off-by: Akhil Repala --- ledger/byron/genesis.go | 8 ++++---- ledger/byron/genesis_test.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ledger/byron/genesis.go b/ledger/byron/genesis.go index 8ebcd245..720a6a47 100644 --- a/ledger/byron/genesis.go +++ b/ledger/byron/genesis.go @@ -26,14 +26,14 @@ import ( "github.com/blinklabs-io/gouroboros/ledger/common" ) -type ByronFtsSeed struct { +type ByronGenesisFtsSeed struct { Value string IsObject bool } type ByronGenesis struct { AvvmDistr map[string]string `json:"avvmDistr"` BlockVersionData ByronGenesisBlockVersionData `json:"blockVersionData"` - FtsSeed ByronFtsSeed `json:"ftsSeed"` + FtsSeed ByronGenesisFtsSeed `json:"ftsSeed"` ProtocolConsts ByronGenesisProtocolConsts `json:"protocolConsts"` StartTime int `json:"startTime"` BootStakeholders map[string]int `json:"bootStakeholders"` @@ -200,7 +200,7 @@ func NewByronGenesisFromFile(path string) (ByronGenesis, error) { } // UnmarshalJSON accepts: "string", {}, or null -func (f *ByronFtsSeed) UnmarshalJSON(b []byte) error { +func (f *ByronGenesisFtsSeed) UnmarshalJSON(b []byte) error { var first byte for _, c := range b { if c > ' ' { @@ -237,7 +237,7 @@ func (f *ByronFtsSeed) UnmarshalJSON(b []byte) error { } } -func (f ByronFtsSeed) MarshalJSON() ([]byte, error) { +func (f ByronGenesisFtsSeed) MarshalJSON() ([]byte, error) { if f.IsObject { // serialize as empty object return []byte(`{}`), nil diff --git a/ledger/byron/genesis_test.go b/ledger/byron/genesis_test.go index a0055e33..22a6a694 100644 --- a/ledger/byron/genesis_test.go +++ b/ledger/byron/genesis_test.go @@ -128,7 +128,7 @@ var expectedGenesisObj = byron.ByronGenesis{ UpdateProposalThd: 100000000000000, UpdateVoteThd: 1000000000000, }, - FtsSeed: byron.ByronFtsSeed{ + FtsSeed: byron.ByronGenesisFtsSeed{ Value: "76617361206f7061736120736b6f766f726f64612047677572646120626f726f64612070726f766f6461", IsObject: false, }, From 32c9688b6c73281b9af7cebd9d20bd6be2ea59a1 Mon Sep 17 00:00:00 2001 From: Akhil Repala Date: Wed, 17 Sep 2025 18:15:16 -0500 Subject: [PATCH 3/3] fix(ledger/byron): removed the first byte checking part from ftsSeed UnmarshalJSON and attempted string, empty object & null Signed-off-by: Akhil Repala --- ledger/byron/genesis.go | 49 ++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/ledger/byron/genesis.go b/ledger/byron/genesis.go index 720a6a47..a18e6904 100644 --- a/ledger/byron/genesis.go +++ b/ledger/byron/genesis.go @@ -200,41 +200,36 @@ func NewByronGenesisFromFile(path string) (ByronGenesis, error) { } // UnmarshalJSON accepts: "string", {}, or null +// Tries each expected shape and accepts the first that parses cleanly func (f *ByronGenesisFtsSeed) UnmarshalJSON(b []byte) error { - var first byte - for _, c := range b { - if c > ' ' { - first = c - break - } - } - switch first { - case '"': - // Normal string case - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } + // Try string + var s string + if err := json.Unmarshal(b, &s); err == nil { f.Value = s f.IsObject = false return nil - case '{': - // empty object case - var m map[string]any - if err := json.Unmarshal(b, &m); err != nil { - return err - } - if len(m) > 0 { - return errors.New("ftsSeed: expected empty object or string") + } + + // Try empty object + var m map[string]any + if err := json.Unmarshal(b, &m); err == nil { + if len(m) == 0 { + f.Value = "" + f.IsObject = true + return nil } + return errors.New("ftsSeed: non-empty object not supported") + } + + // Try null + var v any + if err := json.Unmarshal(b, &v); err == nil && v == nil { f.Value = "" - f.IsObject = true - return nil - case 'n': + f.IsObject = false return nil - default: - return errors.New("ftsSeed: expected string, empty object, or null") } + + return errors.New("ftsSeed: expected string, empty object, or null") } func (f ByronGenesisFtsSeed) MarshalJSON() ([]byte, error) {