From 44c99373770d4d6d867eba22e0e9480185f7940b Mon Sep 17 00:00:00 2001 From: Jenita Date: Sat, 5 Jul 2025 23:24:27 -0500 Subject: [PATCH 01/16] feat: created genesis pools from shelly genesis Signed-off-by: Jenita --- ledger/shelley/genesis.go | 116 +++++++++++++++++++++-- ledger/shelley/genesis_test.go | 165 +++++++++++++++++++++++++++++++++ 2 files changed, 274 insertions(+), 7 deletions(-) diff --git a/ledger/shelley/genesis.go b/ledger/shelley/genesis.go index 0841651d..9717992f 100644 --- a/ledger/shelley/genesis.go +++ b/ledger/shelley/genesis.go @@ -17,6 +17,7 @@ package shelley import ( "encoding/hex" "encoding/json" + "fmt" "io" "math/big" "os" @@ -42,7 +43,40 @@ type ShelleyGenesis struct { ProtocolParameters ShelleyGenesisProtocolParams `json:"protocolParams"` GenDelegs map[string]map[string]string `json:"genDelegs"` InitialFunds map[string]uint64 `json:"initialFunds"` - Staking any `json:"staking"` + Staking GenesisStaking `json:"staking"` +} + +type GenesisStaking struct { + Pools map[string]GenesisPool `json:"pools"` + Stake map[string]string `json:"stake"` +} + +type GenesisPool struct { + Cost int64 `json:"cost"` + Margin float64 `json:"margin"` + Metadata interface{} `json:"metadata"` + Owners []string `json:"owners"` + Pledge int64 `json:"pledge"` + PublicKey string `json:"publicKey"` + Relays []GenesisRelay `json:"relays"` + RewardAccount GenesisReward `json:"rewardAccount"` + Vrf string `json:"vrf"` +} + +type GenesisRelay struct { + SingleHostName *SingleHostName `json:"single host name,omitempty"` +} + +type SingleHostName struct { + DNSName string `json:"dnsName"` + Port int `json:"port"` +} + +type GenesisReward struct { + Credential struct { + KeyHash string `json:"key hash"` + } `json:"credential"` + Network string `json:"network"` } func (g ShelleyGenesis) MarshalCBOR() ([]byte, error) { @@ -65,13 +99,52 @@ func (g ShelleyGenesis) MarshalCBOR() ([]byte, error) { cbor.NewByteString(vrfBytes), } } - staking := []any{} - if g.Staking == nil { - staking = []any{ - map[any]any{}, - map[any]any{}, + + // Convert pools to CBOR format + cborPools := make(map[cbor.ByteString]any) + for poolId, pool := range g.Staking.Pools { + poolIdBytes, err := hex.DecodeString(poolId) + if err != nil { + return nil, err + } + vrfBytes, err := hex.DecodeString(pool.Vrf) + if err != nil { + return nil, err + } + rewardAccountBytes, err := hex.DecodeString(pool.RewardAccount.Credential.KeyHash) + if err != nil { + return nil, err + } + cborPools[cbor.NewByteString(poolIdBytes)] = []any{ + pool.Cost, + pool.Margin, + pool.Pledge, + pool.PublicKey, + []any{ + []byte{0}, + rewardAccountBytes, + }, + pool.Owners, + pool.Relays, + vrfBytes, + pool.Metadata, + } + } + + // Convert stake to CBOR format + cborStake := make(map[cbor.ByteString]cbor.ByteString) + for stakeAddr, poolId := range g.Staking.Stake { + stakeAddrBytes, err := hex.DecodeString(stakeAddr) + if err != nil { + return nil, err } + poolIdBytes, err := hex.DecodeString(poolId) + if err != nil { + return nil, err + } + cborStake[cbor.NewByteString(stakeAddrBytes)] = cbor.NewByteString(poolIdBytes) } + slotLengthMs := &big.Rat{} tmpData := []any{ []any{ @@ -95,7 +168,10 @@ func (g ShelleyGenesis) MarshalCBOR() ([]byte, error) { g.ProtocolParameters, genDelegs, g.InitialFunds, - staking, + []any{ + cborPools, + cborStake, + }, } return cbor.Encode(tmpData) } @@ -128,6 +204,32 @@ func (g *ShelleyGenesis) GenesisUtxos() ([]common.Utxo, error) { return ret, nil } +// GetInitialPools returns all initial stake pools with their delegators +func (g *ShelleyGenesis) GetInitialPools() (map[string]GenesisPool, map[string][]string, error) { + poolStake := make(map[string][]string) + for stakeAddr, poolId := range g.Staking.Stake { + poolStake[poolId] = append(poolStake[poolId], stakeAddr) + } + return g.Staking.Pools, poolStake, nil +} + +// GetPoolById returns a specific pool by its ID along with its delegators +func (g *ShelleyGenesis) GetPoolById(poolId string) (*GenesisPool, []string, error) { + pool, exists := g.Staking.Pools[poolId] + if !exists { + return nil, nil, fmt.Errorf("pool not found") + } + + var delegators []string + for stakeAddr, pId := range g.Staking.Stake { + if pId == poolId { + delegators = append(delegators, stakeAddr) + } + } + + return &pool, delegators, nil +} + type ShelleyGenesisProtocolParams struct { cbor.StructAsArray MinFeeA uint `json:"minFeeA"` diff --git a/ledger/shelley/genesis_test.go b/ledger/shelley/genesis_test.go index 35926576..6410b9a5 100644 --- a/ledger/shelley/genesis_test.go +++ b/ledger/shelley/genesis_test.go @@ -250,3 +250,168 @@ func TestGenesisUtxos(t *testing.T) { ) } } + +const shelleyGenesisWithStaking = ` +{ + "activeSlotsCoeff": 0.05, + "protocolParams": { + "protocolVersion": { + "minor": 0, + "major": 2 + }, + "decentralisationParam": 1, + "eMax": 18, + "extraEntropy": { + "tag": "NeutralNonce" + }, + "maxTxSize": 16384, + "maxBlockBodySize": 65536, + "maxBlockHeaderSize": 1100, + "minFeeA": 44, + "minFeeB": 155381, + "minUTxOValue": 1000000, + "poolDeposit": 500000000, + "minPoolCost": 340000000, + "keyDeposit": 2000000, + "nOpt": 150, + "rho": 0.003, + "tau": 0.20, + "a0": 0.3 + }, + "genDelegs": {}, + "updateQuorum": 5, + "networkId": "Testnet", + "initialFunds": {}, + "maxLovelaceSupply": 45000000000000000, + "networkMagic": 764824073, + "epochLength": 432000, + "systemStart": "2017-09-23T21:44:51Z", + "slotsPerKESPeriod": 129600, + "slotLength": 1, + "maxKESEvolutions": 62, + "securityParam": 2160, + "staking": { + "pools": { + "0aedc455785463235311c990f68742c9043cd79af09ab31c2ba5e195": { + "cost": 340000000, + "margin": 0.0, + "metadata": null, + "owners": [], + "pledge": 0, + "publicKey": "0aedc455785463235311c990f68742c9043cd79af09ab31c2ba5e195", + "relays": [ + { + "single host name": { + "dnsName": "p4.example", + "port": 3001 + } + } + ], + "rewardAccount": { + "credential": { + "key hash": "6079cde665c2035b8d9ac8929307bdd7f20a51e678e9d4a5e39ace3a" + }, + "network": "Testnet" + }, + "vrf": "eb53a17fbad9b7ea0bcf1e1ea89355305600d593b426dfc3084a924d8877d47e" + } + }, + "stake": { + "24632b71152f31516054075897d0d4ababc33204f8a8661136d49e36": "0aedc455785463235311c990f68742c9043cd79af09ab31c2ba5e195" + } + } +} +` + +func TestGenesisStaking(t *testing.T) { + tmpGenesis, err := shelley.NewShelleyGenesisFromReader( + strings.NewReader(shelleyGenesisWithStaking), + ) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + // Test GetInitialPools + pools, delegators, err := tmpGenesis.GetInitialPools() + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + expectedPoolId := "0aedc455785463235311c990f68742c9043cd79af09ab31c2ba5e195" + if len(pools) != 1 { + t.Fatalf("expected 1 pool, got %d", len(pools)) + } + + pool, exists := pools[expectedPoolId] + if !exists { + t.Fatalf("expected pool with ID %s not found", expectedPoolId) + } + + // Verify pool details + if pool.Cost != 340000000 { + t.Errorf("expected pool cost 340000000, got %d", pool.Cost) + } + if pool.Margin != 0.0 { + t.Errorf("expected pool margin 0.0, got %f", pool.Margin) + } + if pool.Pledge != 0 { + t.Errorf("expected pool pledge 0, got %d", pool.Pledge) + } + if pool.PublicKey != expectedPoolId { + t.Errorf("expected pool public key %s, got %s", expectedPoolId, pool.PublicKey) + } + if pool.Vrf != "eb53a17fbad9b7ea0bcf1e1ea89355305600d593b426dfc3084a924d8877d47e" { + t.Errorf("unexpected pool VRF key") + } + + // Verify delegators + expectedStakeAddr := "24632b71152f31516054075897d0d4ababc33204f8a8661136d49e36" + if len(delegators[expectedPoolId]) != 1 { + t.Fatalf("expected 1 delegator for pool, got %d", len(delegators[expectedPoolId])) + } + if delegators[expectedPoolId][0] != expectedStakeAddr { + t.Errorf("expected stake address %s, got %s", expectedStakeAddr, delegators[expectedPoolId][0]) + } + + // Test GetPoolById + poolById, delegatorsById, err := tmpGenesis.GetPoolById(expectedPoolId) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if poolById.PublicKey != expectedPoolId { + t.Errorf("GetPoolById: expected pool public key %s, got %s", expectedPoolId, poolById.PublicKey) + } + + if len(delegatorsById) != 1 || delegatorsById[0] != expectedStakeAddr { + t.Errorf("GetPoolById: unexpected delegators") + } + + // Test non-existent pool + _, _, err = tmpGenesis.GetPoolById("nonexistentpoolid") + if err == nil { + t.Error("expected error for non-existent pool, got nil") + } else if err.Error() != "pool not found" { + t.Errorf("expected 'pool not found' error, got: %v", err) + } +} + +func TestGenesisStakingCBOR(t *testing.T) { + tmpGenesis, err := shelley.NewShelleyGenesisFromReader( + strings.NewReader(shelleyGenesisWithStaking), + ) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + // Test CBOR marshaling + cborData, err := tmpGenesis.MarshalCBOR() + if err != nil { + t.Fatalf("unexpected error during CBOR marshaling: %s", err) + } + + if len(cborData) == 0 { + t.Error("expected non-empty CBOR data, got empty") + } + +} From 4f595532d9e02d9dac2b825c894fd1fa9f27d497 Mon Sep 17 00:00:00 2001 From: Jenita Date: Sat, 5 Jul 2025 23:31:55 -0500 Subject: [PATCH 02/16] feat: added error handling to Utxprpc() Signed-off-by: Jenita --- ledger/shelley/genesis.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ledger/shelley/genesis.go b/ledger/shelley/genesis.go index 9717992f..510a0b17 100644 --- a/ledger/shelley/genesis.go +++ b/ledger/shelley/genesis.go @@ -17,7 +17,7 @@ package shelley import ( "encoding/hex" "encoding/json" - "fmt" + "errors" "io" "math/big" "os" @@ -217,7 +217,7 @@ func (g *ShelleyGenesis) GetInitialPools() (map[string]GenesisPool, map[string][ func (g *ShelleyGenesis) GetPoolById(poolId string) (*GenesisPool, []string, error) { pool, exists := g.Staking.Pools[poolId] if !exists { - return nil, nil, fmt.Errorf("pool not found") + return nil, nil, errors.New("pool not found") } var delegators []string From c44fbd32893c5667905b6ddce3f4e5907492770e Mon Sep 17 00:00:00 2001 From: Jenita Date: Fri, 18 Jul 2025 17:40:24 -0500 Subject: [PATCH 03/16] feat: created genesis pools from shelly genesis Signed-off-by: Jenita --- ledger/common/certs.go | 55 +++++++ ledger/shelley/genesis.go | 222 +++++++++++++++++++++------- ledger/shelley/genesis_test.go | 262 +++++++++++++++------------------ 3 files changed, 340 insertions(+), 199 deletions(-) diff --git a/ledger/common/certs.go b/ledger/common/certs.go index 05b8a528..3e92fb4c 100644 --- a/ledger/common/certs.go +++ b/ledger/common/certs.go @@ -15,8 +15,11 @@ package common import ( + "encoding/hex" + "encoding/json" "errors" "fmt" + "math/big" "net" "github.com/blinklabs-io/gouroboros/cbor" @@ -387,6 +390,58 @@ type PoolRegistrationCertificate struct { PoolMetadata *PoolMetadata } +func (p *PoolRegistrationCertificate) UnmarshalJSON(data []byte) error { + type Alias PoolRegistrationCertificate + aux := &struct { + Margin interface{} `json:"margin"` + RewardAccount struct { + Credential struct { + KeyHash string `json:"key hash"` + } `json:"credential"` + Network string `json:"network"` + } `json:"rewardAccount"` + *Alias + }{ + Alias: (*Alias)(p), + } + + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + // Handle margin field + switch v := aux.Margin.(type) { + case float64: + p.Margin.Rat = new(big.Rat).SetFloat64(v) + case []interface{}: + if len(v) == 2 { + if num, ok := v[0].(float64); ok { + if den, ok := v[1].(float64); ok { + p.Margin.Rat = new(big.Rat).SetFrac64(int64(num), int64(den)) + } + } + } + default: + p.Margin.Rat = new(big.Rat).SetInt64(0) + } + + // Handle reward account + if aux.RewardAccount.Credential.KeyHash != "" { + hashBytes, err := hex.DecodeString(aux.RewardAccount.Credential.KeyHash) + if err != nil { + return fmt.Errorf("failed to decode reward account key hash: %w", err) + } + if len(hashBytes) != 28 { + return fmt.Errorf("invalid key hash length: expected 28, got %d", len(hashBytes)) + } + var hash Blake2b224 + copy(hash[:], hashBytes) + p.RewardAccount = hash + } + + return nil +} + func (c PoolRegistrationCertificate) isCertificate() {} func (c *PoolRegistrationCertificate) UnmarshalCBOR(cborData []byte) error { diff --git a/ledger/shelley/genesis.go b/ledger/shelley/genesis.go index 510a0b17..95d96068 100644 --- a/ledger/shelley/genesis.go +++ b/ledger/shelley/genesis.go @@ -18,9 +18,11 @@ import ( "encoding/hex" "encoding/json" "errors" + "fmt" "io" "math/big" "os" + "reflect" "time" "github.com/blinklabs-io/gouroboros/cbor" @@ -47,36 +49,8 @@ type ShelleyGenesis struct { } type GenesisStaking struct { - Pools map[string]GenesisPool `json:"pools"` - Stake map[string]string `json:"stake"` -} - -type GenesisPool struct { - Cost int64 `json:"cost"` - Margin float64 `json:"margin"` - Metadata interface{} `json:"metadata"` - Owners []string `json:"owners"` - Pledge int64 `json:"pledge"` - PublicKey string `json:"publicKey"` - Relays []GenesisRelay `json:"relays"` - RewardAccount GenesisReward `json:"rewardAccount"` - Vrf string `json:"vrf"` -} - -type GenesisRelay struct { - SingleHostName *SingleHostName `json:"single host name,omitempty"` -} - -type SingleHostName struct { - DNSName string `json:"dnsName"` - Port int `json:"port"` -} - -type GenesisReward struct { - Credential struct { - KeyHash string `json:"key hash"` - } `json:"credential"` - Network string `json:"network"` + Pools map[string]common.PoolRegistrationCertificate `json:"pools"` + Stake map[string]string `json:"stake"` } func (g ShelleyGenesis) MarshalCBOR() ([]byte, error) { @@ -107,27 +81,21 @@ func (g ShelleyGenesis) MarshalCBOR() ([]byte, error) { if err != nil { return nil, err } - vrfBytes, err := hex.DecodeString(pool.Vrf) - if err != nil { - return nil, err - } - rewardAccountBytes, err := hex.DecodeString(pool.RewardAccount.Credential.KeyHash) - if err != nil { - return nil, err - } + vrfBytes := pool.VrfKeyHash.Bytes() + rewardAccountBytes := pool.RewardAccount.Bytes() cborPools[cbor.NewByteString(poolIdBytes)] = []any{ pool.Cost, pool.Margin, pool.Pledge, - pool.PublicKey, + pool.Operator.Bytes(), []any{ []byte{0}, rewardAccountBytes, }, - pool.Owners, - pool.Relays, + convertAddrKeyHashesToBytes(pool.PoolOwners), + convertPoolRelays(pool.Relays), vrfBytes, - pool.Metadata, + pool.PoolMetadata, } } @@ -176,6 +144,67 @@ func (g ShelleyGenesis) MarshalCBOR() ([]byte, error) { return cbor.Encode(tmpData) } +func convertAddrKeyHashesToBytes(hashes []common.AddrKeyHash) [][]byte { + result := make([][]byte, len(hashes)) + for i, h := range hashes { + result[i] = h.Bytes() + } + return result +} + +func convertPoolRelays(relays []common.PoolRelay) []any { + result := make([]any, len(relays)) + for i, relay := range relays { + switch relay.Type { + case 0: // SingleHostAddr + var ipv4, ipv6 []byte + var port uint32 + if relay.Ipv4 != nil { + ipv4 = relay.Ipv4.To4() + } + if relay.Ipv6 != nil { + ipv6 = relay.Ipv6.To16() + } + if relay.Port != nil { + port = *relay.Port + } + result[i] = map[string]any{ + "single host addr": []any{ + ipv4, + ipv6, + port, + }, + } + case 1: // SingleHostName + var hostname string + var port uint32 + if relay.Hostname != nil { + hostname = *relay.Hostname + } + if relay.Port != nil { + port = *relay.Port + } + result[i] = map[string]any{ + "single host name": []any{ + hostname, + port, + }, + } + case 2: // MultiHostName + var hostname string + if relay.Hostname != nil { + hostname = *relay.Hostname + } + result[i] = map[string]any{ + "multi host name": hostname, + } + default: + result[i] = nil + } + } + return result +} + func (g *ShelleyGenesis) GenesisUtxos() ([]common.Utxo, error) { ret := []common.Utxo{} for address, amount := range g.InitialFunds { @@ -204,30 +233,119 @@ func (g *ShelleyGenesis) GenesisUtxos() ([]common.Utxo, error) { return ret, nil } -// GetInitialPools returns all initial stake pools with their delegators -func (g *ShelleyGenesis) GetInitialPools() (map[string]GenesisPool, map[string][]string, error) { - poolStake := make(map[string][]string) +// InitialPools returns all pools and their delegators from the genesis data +func (g *ShelleyGenesis) InitialPools() (map[string]common.PoolRegistrationCertificate, map[string][]common.Address, error) { + + pools := make(map[string]common.PoolRegistrationCertificate) + poolStake := make(map[string][]common.Address) + + // Check for empty staking data + if reflect.DeepEqual(g.Staking, GenesisStaking{}) { + return pools, poolStake, nil + } + + // Process stake delegations first for stakeAddr, poolId := range g.Staking.Stake { - poolStake[poolId] = append(poolStake[poolId], stakeAddr) + // Validate stake address format + if len(stakeAddr) != 56 { + return nil, nil, fmt.Errorf("invalid stake address length: %d (expected 56 hex chars)", len(stakeAddr)) + } + + stakeKeyBytes, err := hex.DecodeString(stakeAddr) + if err != nil { + return nil, nil, fmt.Errorf("failed to decode stake key %s: %w", stakeAddr, err) + } + + // Create stake address + stakeAddrBytes := append([]byte{0xE1}, stakeKeyBytes...) + addr, err := common.NewAddressFromBytes(stakeAddrBytes) + if err != nil { + return nil, nil, fmt.Errorf("failed to create stake address: %w", err) + } + + poolStake[poolId] = append(poolStake[poolId], addr) + } + + // Process pools + for poolId, pool := range g.Staking.Pools { + // Validate pool ID format + if len(poolId) != 56 { + return nil, nil, fmt.Errorf("invalid pool ID length: %d (expected 56 hex chars)", len(poolId)) + } + + operatorBytes, err := hex.DecodeString(poolId) + if err != nil { + return nil, nil, fmt.Errorf("failed to decode pool operator key: %w", err) + } + + pools[poolId] = common.PoolRegistrationCertificate{ + Operator: common.Blake2b224(operatorBytes), + VrfKeyHash: pool.VrfKeyHash, + Pledge: pool.Pledge, + Cost: pool.Cost, + Margin: pool.Margin, + RewardAccount: pool.RewardAccount, + PoolOwners: pool.PoolOwners, + Relays: pool.Relays, + PoolMetadata: pool.PoolMetadata, + } } - return g.Staking.Pools, poolStake, nil + + return pools, poolStake, nil } -// GetPoolById returns a specific pool by its ID along with its delegators -func (g *ShelleyGenesis) GetPoolById(poolId string) (*GenesisPool, []string, error) { +// PoolById returns a specific pool by its ID along with its delegators +func (g *ShelleyGenesis) PoolById(poolId string) (*common.PoolRegistrationCertificate, []common.Address, error) { + // Validate input + if len(poolId) != 56 { + return nil, nil, errors.New("invalid pool ID length") + } + pool, exists := g.Staking.Pools[poolId] if !exists { return nil, nil, errors.New("pool not found") } - var delegators []string + // Decode pool operator key + operatorBytes, err := hex.DecodeString(poolId) + if err != nil { + return nil, nil, errors.New("failed to decode pool operator key") + } + + var delegators []common.Address for stakeAddr, pId := range g.Staking.Stake { if pId == poolId { - delegators = append(delegators, stakeAddr) + if len(stakeAddr) != 56 { + return nil, nil, errors.New("invalid stake address length") + } + + stakeKeyBytes, err := hex.DecodeString(stakeAddr) + if err != nil { + return nil, nil, errors.New("failed to decode stake key") + } + + stakeAddrBytes := append([]byte{0xE1}, stakeKeyBytes...) + addr, err := common.NewAddressFromBytes(stakeAddrBytes) + if err != nil { + return nil, nil, errors.New("failed to create stake address") + } + + delegators = append(delegators, addr) } } - return &pool, delegators, nil + // Return pool with delegators + return &common.PoolRegistrationCertificate{ + Operator: common.Blake2b224(operatorBytes), + VrfKeyHash: pool.VrfKeyHash, + Pledge: pool.Pledge, + Cost: pool.Cost, + Margin: pool.Margin, + RewardAccount: pool.RewardAccount, + PoolOwners: pool.PoolOwners, + Relays: pool.Relays, + PoolMetadata: pool.PoolMetadata, + }, delegators, nil } type ShelleyGenesisProtocolParams struct { diff --git a/ledger/shelley/genesis_test.go b/ledger/shelley/genesis_test.go index 6410b9a5..057d7186 100644 --- a/ledger/shelley/genesis_test.go +++ b/ledger/shelley/genesis_test.go @@ -15,6 +15,8 @@ package shelley_test import ( + //"bytes" + "encoding/hex" "encoding/json" "math/big" "reflect" @@ -251,167 +253,133 @@ func TestGenesisUtxos(t *testing.T) { } } -const shelleyGenesisWithStaking = ` -{ - "activeSlotsCoeff": 0.05, - "protocolParams": { - "protocolVersion": { - "minor": 0, - "major": 2 - }, - "decentralisationParam": 1, - "eMax": 18, - "extraEntropy": { - "tag": "NeutralNonce" - }, - "maxTxSize": 16384, - "maxBlockBodySize": 65536, - "maxBlockHeaderSize": 1100, - "minFeeA": 44, - "minFeeB": 155381, - "minUTxOValue": 1000000, - "poolDeposit": 500000000, - "minPoolCost": 340000000, - "keyDeposit": 2000000, - "nOpt": 150, - "rho": 0.003, - "tau": 0.20, - "a0": 0.3 - }, - "genDelegs": {}, - "updateQuorum": 5, - "networkId": "Testnet", - "initialFunds": {}, - "maxLovelaceSupply": 45000000000000000, - "networkMagic": 764824073, - "epochLength": 432000, - "systemStart": "2017-09-23T21:44:51Z", - "slotsPerKESPeriod": 129600, - "slotLength": 1, - "maxKESEvolutions": 62, - "securityParam": 2160, - "staking": { - "pools": { - "0aedc455785463235311c990f68742c9043cd79af09ab31c2ba5e195": { - "cost": 340000000, - "margin": 0.0, - "metadata": null, - "owners": [], - "pledge": 0, - "publicKey": "0aedc455785463235311c990f68742c9043cd79af09ab31c2ba5e195", - "relays": [ - { - "single host name": { - "dnsName": "p4.example", - "port": 3001 +func TestGenesisStaking(t *testing.T) { + const testGenesis = `{ + "systemStart": "2017-09-23T21:44:51Z", + "networkMagic": 764824073, + "staking": { + "pools": { + "0aedc455785463235311c990f68742c9043cd79af09ab31c2ba5e195": { + "cost": 340000000, + "margin": 0.0, + "pledge": 0, + "publicKey": "0aedc455785463235311c990f68742c9043cd79af09ab31c2ba5e195", + "vrf": "eb53a17fbad9b7ea0bcf1e1ea89355305600d593b426dfc3084a924d8877d47e", + "rewardAccount": { + "credential": { + "key hash": "6079cde665c2035b8d9ac8929307bdd7f20a51e678e9d4a5e39ace3a" + }, + "network": "Testnet" + } + } + }, + "stake": { + "24632b71152f31516054075897d0d4ababc33204f8a8661136d49e36": "0aedc455785463235311c990f68742c9043cd79af09ab31c2ba5e195" } - } - ], - "rewardAccount": { - "credential": { - "key hash": "6079cde665c2035b8d9ac8929307bdd7f20a51e678e9d4a5e39ace3a" - }, - "network": "Testnet" }, - "vrf": "eb53a17fbad9b7ea0bcf1e1ea89355305600d593b426dfc3084a924d8877d47e" - } - }, - "stake": { - "24632b71152f31516054075897d0d4ababc33204f8a8661136d49e36": "0aedc455785463235311c990f68742c9043cd79af09ab31c2ba5e195" - } - } -} -` + "protocolParams": { + "minFeeA": 44, + "minFeeB": 155381, + "maxBlockBodySize": 65536, + "maxTxSize": 16384, + "maxBlockHeaderSize": 1100, + "keyDeposit": 2000000, + "poolDeposit": 500000000 + } + }` -func TestGenesisStaking(t *testing.T) { - tmpGenesis, err := shelley.NewShelleyGenesisFromReader( - strings.NewReader(shelleyGenesisWithStaking), - ) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } + t.Run("TestInitialPools", func(t *testing.T) { + genesis, err := shelley.NewShelleyGenesisFromReader(strings.NewReader(testGenesis)) + if err != nil { + t.Fatalf("Genesis parsing failed: %v", err) + } - // Test GetInitialPools - pools, delegators, err := tmpGenesis.GetInitialPools() - if err != nil { - t.Fatalf("unexpected error: %s", err) - } + pools, delegators, err := genesis.InitialPools() + if err != nil { + t.Fatalf("InitialPools failed: %v", err) + } - expectedPoolId := "0aedc455785463235311c990f68742c9043cd79af09ab31c2ba5e195" - if len(pools) != 1 { - t.Fatalf("expected 1 pool, got %d", len(pools)) - } + expectedPoolId := "0aedc455785463235311c990f68742c9043cd79af09ab31c2ba5e195" + expectedStakeKey := "24632b71152f31516054075897d0d4ababc33204f8a8661136d49e36" - pool, exists := pools[expectedPoolId] - if !exists { - t.Fatalf("expected pool with ID %s not found", expectedPoolId) - } + // Test pool count + if len(pools) != 1 { + t.Errorf("Expected 1 pool, got %d", len(pools)) + } - // Verify pool details - if pool.Cost != 340000000 { - t.Errorf("expected pool cost 340000000, got %d", pool.Cost) - } - if pool.Margin != 0.0 { - t.Errorf("expected pool margin 0.0, got %f", pool.Margin) - } - if pool.Pledge != 0 { - t.Errorf("expected pool pledge 0, got %d", pool.Pledge) - } - if pool.PublicKey != expectedPoolId { - t.Errorf("expected pool public key %s, got %s", expectedPoolId, pool.PublicKey) - } - if pool.Vrf != "eb53a17fbad9b7ea0bcf1e1ea89355305600d593b426dfc3084a924d8877d47e" { - t.Errorf("unexpected pool VRF key") - } + // Test pool data + pool, exists := pools[expectedPoolId] + if !exists { + t.Fatal("Expected pool not found") + } - // Verify delegators - expectedStakeAddr := "24632b71152f31516054075897d0d4ababc33204f8a8661136d49e36" - if len(delegators[expectedPoolId]) != 1 { - t.Fatalf("expected 1 delegator for pool, got %d", len(delegators[expectedPoolId])) - } - if delegators[expectedPoolId][0] != expectedStakeAddr { - t.Errorf("expected stake address %s, got %s", expectedStakeAddr, delegators[expectedPoolId][0]) - } + if pool.Cost != 340000000 { + t.Errorf("Expected pool cost 340000000, got %d", pool.Cost) + } - // Test GetPoolById - poolById, delegatorsById, err := tmpGenesis.GetPoolById(expectedPoolId) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } + // Test delegators + if len(delegators) != 1 { + t.Errorf("Expected 1 delegator mapping, got %d", len(delegators)) + } - if poolById.PublicKey != expectedPoolId { - t.Errorf("GetPoolById: expected pool public key %s, got %s", expectedPoolId, poolById.PublicKey) - } + delegs := delegators[expectedPoolId] + if len(delegs) != 1 { + t.Errorf("Expected 1 delegator, got %d", len(delegs)) + } else { + // Extract stake key from address + addrBytes, _ := delegs[0].Bytes() + if len(addrBytes) != 29 || addrBytes[0] != 0xE1 { + t.Error("Delegator address is not in expected stake address format") + } else { + stakeKey := hex.EncodeToString(addrBytes[1:]) + if stakeKey != expectedStakeKey { + t.Errorf("Delegator key mismatch:\nExpected: %s\nActual: %s", + expectedStakeKey, stakeKey) + } + } + } + }) - if len(delegatorsById) != 1 || delegatorsById[0] != expectedStakeAddr { - t.Errorf("GetPoolById: unexpected delegators") - } + t.Run("TestPoolById", func(t *testing.T) { + genesis, err := shelley.NewShelleyGenesisFromReader(strings.NewReader(testGenesis)) + if err != nil { + t.Fatalf("Genesis parsing failed: %v", err) + } - // Test non-existent pool - _, _, err = tmpGenesis.GetPoolById("nonexistentpoolid") - if err == nil { - t.Error("expected error for non-existent pool, got nil") - } else if err.Error() != "pool not found" { - t.Errorf("expected 'pool not found' error, got: %v", err) - } -} + expectedPoolId := "0aedc455785463235311c990f68742c9043cd79af09ab31c2ba5e195" + expectedStakeKey := "24632b71152f31516054075897d0d4ababc33204f8a8661136d49e36" -func TestGenesisStakingCBOR(t *testing.T) { - tmpGenesis, err := shelley.NewShelleyGenesisFromReader( - strings.NewReader(shelleyGenesisWithStaking), - ) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } + pool, delegators, err := genesis.PoolById(expectedPoolId) + if err != nil { + t.Fatalf("PoolById failed: %v", err) + } - // Test CBOR marshaling - cborData, err := tmpGenesis.MarshalCBOR() - if err != nil { - t.Fatalf("unexpected error during CBOR marshaling: %s", err) - } + // Test pool data + if pool.Cost != 340000000 { + t.Errorf("Expected pool cost 340000000, got %d", pool.Cost) + } - if len(cborData) == 0 { - t.Error("expected non-empty CBOR data, got empty") - } + // Test delegators + if len(delegators) != 1 { + t.Errorf("Expected 1 delegator, got %d", len(delegators)) + } else { + // Extract stake key from address + addrBytes, _ := delegators[0].Bytes() + if len(addrBytes) != 29 || addrBytes[0] != 0xE1 { + t.Error("Delegator address is not in expected stake address format") + } else { + stakeKey := hex.EncodeToString(addrBytes[1:]) + if stakeKey != expectedStakeKey { + t.Errorf("Delegator key mismatch:\nExpected: %s\nActual: %s", + expectedStakeKey, stakeKey) + } + } + } + // Test non-existent pool + _, _, err = genesis.PoolById("nonexistentpoolid") + if err == nil { + t.Error("Expected error for non-existent pool, got nil") + } + }) } From 0f9eabb163890bb5dde7635854833a1434b8b900 Mon Sep 17 00:00:00 2001 From: Jenita Date: Fri, 18 Jul 2025 17:49:59 -0500 Subject: [PATCH 04/16] feat: created genesis pools from shelly genesis Signed-off-by: Jenita --- ledger/shelley/genesis.go | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/ledger/shelley/genesis.go b/ledger/shelley/genesis.go index 95d96068..aa479ca5 100644 --- a/ledger/shelley/genesis.go +++ b/ledger/shelley/genesis.go @@ -18,7 +18,6 @@ import ( "encoding/hex" "encoding/json" "errors" - "fmt" "io" "math/big" "os" @@ -235,47 +234,40 @@ func (g *ShelleyGenesis) GenesisUtxos() ([]common.Utxo, error) { // InitialPools returns all pools and their delegators from the genesis data func (g *ShelleyGenesis) InitialPools() (map[string]common.PoolRegistrationCertificate, map[string][]common.Address, error) { - pools := make(map[string]common.PoolRegistrationCertificate) poolStake := make(map[string][]common.Address) - // Check for empty staking data if reflect.DeepEqual(g.Staking, GenesisStaking{}) { return pools, poolStake, nil } - // Process stake delegations first for stakeAddr, poolId := range g.Staking.Stake { - // Validate stake address format if len(stakeAddr) != 56 { - return nil, nil, fmt.Errorf("invalid stake address length: %d (expected 56 hex chars)", len(stakeAddr)) + return nil, nil, errors.New("invalid stake address length") } stakeKeyBytes, err := hex.DecodeString(stakeAddr) if err != nil { - return nil, nil, fmt.Errorf("failed to decode stake key %s: %w", stakeAddr, err) + return nil, nil, errors.New("failed to decode stake key") } - // Create stake address stakeAddrBytes := append([]byte{0xE1}, stakeKeyBytes...) addr, err := common.NewAddressFromBytes(stakeAddrBytes) if err != nil { - return nil, nil, fmt.Errorf("failed to create stake address: %w", err) + return nil, nil, errors.New("failed to create stake address") } poolStake[poolId] = append(poolStake[poolId], addr) } - // Process pools for poolId, pool := range g.Staking.Pools { - // Validate pool ID format if len(poolId) != 56 { - return nil, nil, fmt.Errorf("invalid pool ID length: %d (expected 56 hex chars)", len(poolId)) + return nil, nil, errors.New("invalid pool ID length") } operatorBytes, err := hex.DecodeString(poolId) if err != nil { - return nil, nil, fmt.Errorf("failed to decode pool operator key: %w", err) + return nil, nil, errors.New("failed to decode pool operator key") } pools[poolId] = common.PoolRegistrationCertificate{ @@ -296,7 +288,6 @@ func (g *ShelleyGenesis) InitialPools() (map[string]common.PoolRegistrationCerti // PoolById returns a specific pool by its ID along with its delegators func (g *ShelleyGenesis) PoolById(poolId string) (*common.PoolRegistrationCertificate, []common.Address, error) { - // Validate input if len(poolId) != 56 { return nil, nil, errors.New("invalid pool ID length") } @@ -306,7 +297,6 @@ func (g *ShelleyGenesis) PoolById(poolId string) (*common.PoolRegistrationCertif return nil, nil, errors.New("pool not found") } - // Decode pool operator key operatorBytes, err := hex.DecodeString(poolId) if err != nil { return nil, nil, errors.New("failed to decode pool operator key") @@ -334,7 +324,6 @@ func (g *ShelleyGenesis) PoolById(poolId string) (*common.PoolRegistrationCertif } } - // Return pool with delegators return &common.PoolRegistrationCertificate{ Operator: common.Blake2b224(operatorBytes), VrfKeyHash: pool.VrfKeyHash, From e5340d3ab3c706e82d4ded947635f8a3aded48ae Mon Sep 17 00:00:00 2001 From: Jenita Date: Fri, 18 Jul 2025 17:54:30 -0500 Subject: [PATCH 05/16] feat: created genesis pools from shelly genesis Signed-off-by: Jenita --- ledger/common/certs.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/ledger/common/certs.go b/ledger/common/certs.go index 3e92fb4c..847e85f2 100644 --- a/ledger/common/certs.go +++ b/ledger/common/certs.go @@ -376,18 +376,18 @@ func (p *PoolRelay) Utxorpc() (*utxorpc.Relay, error) { } type PoolRegistrationCertificate struct { - cbor.StructAsArray - cbor.DecodeStoreCbor - CertType uint - Operator PoolKeyHash - VrfKeyHash VrfKeyHash - Pledge uint64 - Cost uint64 - Margin cbor.Rat - RewardAccount AddrKeyHash - PoolOwners []AddrKeyHash - Relays []PoolRelay - PoolMetadata *PoolMetadata + cbor.StructAsArray `json:"-"` + cbor.DecodeStoreCbor `json:"-"` + CertType uint `json:"certType,omitempty"` + Operator PoolKeyHash `json:"operator"` + VrfKeyHash VrfKeyHash `json:"vrfKeyHash"` + Pledge uint64 `json:"pledge"` + Cost uint64 `json:"cost"` + Margin cbor.Rat `json:"margin"` + RewardAccount AddrKeyHash `json:"rewardAccount"` + PoolOwners []AddrKeyHash `json:"poolOwners"` + Relays []PoolRelay `json:"relays"` + PoolMetadata *PoolMetadata `json:"poolMetadata,omitempty"` } func (p *PoolRegistrationCertificate) UnmarshalJSON(data []byte) error { From c8749751951aa00320a2e67339095fc3f164609d Mon Sep 17 00:00:00 2001 From: Jenita Date: Fri, 18 Jul 2025 18:10:36 -0500 Subject: [PATCH 06/16] feat: created genesis pools from shelly genesis Signed-off-by: Jenita --- ledger/common/certs.go | 103 ++++++++++++++++++++++++--------- ledger/shelley/genesis_test.go | 1 - 2 files changed, 76 insertions(+), 28 deletions(-) diff --git a/ledger/common/certs.go b/ledger/common/certs.go index 847e85f2..46bdc4fd 100644 --- a/ledger/common/certs.go +++ b/ledger/common/certs.go @@ -392,51 +392,100 @@ type PoolRegistrationCertificate struct { func (p *PoolRegistrationCertificate) UnmarshalJSON(data []byte) error { type Alias PoolRegistrationCertificate + + // Temporary struct for initial unmarshaling aux := &struct { - Margin interface{} `json:"margin"` - RewardAccount struct { - Credential struct { - KeyHash string `json:"key hash"` - } `json:"credential"` - Network string `json:"network"` - } `json:"rewardAccount"` + Operator string `json:"operator"` + VrfKeyHash string `json:"vrfKeyHash"` + Pledge uint64 `json:"pledge"` + Cost uint64 `json:"cost"` + Margin json.RawMessage `json:"margin"` + RewardAccount json.RawMessage `json:"rewardAccount"` + PoolOwners []string `json:"poolOwners"` + Relays []PoolRelay `json:"relays"` + PoolMetadata *PoolMetadata `json:"poolMetadata,omitempty"` *Alias }{ Alias: (*Alias)(p), } - if err := json.Unmarshal(data, &aux); err != nil { - return err + if err := json.Unmarshal(data, aux); err != nil { + return fmt.Errorf("failed to unmarshal pool registration: %w", err) } + p.Cost = aux.Cost + // Handle margin field - switch v := aux.Margin.(type) { - case float64: - p.Margin.Rat = new(big.Rat).SetFloat64(v) - case []interface{}: - if len(v) == 2 { - if num, ok := v[0].(float64); ok { - if den, ok := v[1].(float64); ok { - p.Margin.Rat = new(big.Rat).SetFrac64(int64(num), int64(den)) + if len(aux.Margin) > 0 { + var marginValue interface{} + if err := json.Unmarshal(aux.Margin, &marginValue); err != nil { + return fmt.Errorf("failed to unmarshal margin: %w", err) + } + + switch v := marginValue.(type) { + case float64: + p.Margin.Rat = new(big.Rat).SetFloat64(v) + case []interface{}: + if len(v) == 2 { + if num, ok := v[0].(float64); ok { + if den, ok := v[1].(float64); ok && den != 0 { + p.Margin.Rat = new(big.Rat).SetFrac64(int64(num), int64(den)) + } } } } - default: - p.Margin.Rat = new(big.Rat).SetInt64(0) } // Handle reward account - if aux.RewardAccount.Credential.KeyHash != "" { - hashBytes, err := hex.DecodeString(aux.RewardAccount.Credential.KeyHash) + if len(aux.RewardAccount) > 0 { + var rewardAccount struct { + Credential struct { + KeyHash string `json:"key hash"` + } `json:"credential"` + } + if err := json.Unmarshal(aux.RewardAccount, &rewardAccount); err != nil { + return fmt.Errorf("failed to unmarshal reward account: %w", err) + } + + if rewardAccount.Credential.KeyHash != "" { + hashBytes, err := hex.DecodeString(rewardAccount.Credential.KeyHash) + if err != nil { + return fmt.Errorf("failed to decode reward account key hash: %w", err) + } + var hash Blake2b224 + copy(hash[:], hashBytes) + p.RewardAccount = AddrKeyHash(hash) + } + } + + // Convert string fields to binary types + if aux.Operator != "" { + opBytes, err := hex.DecodeString(aux.Operator) if err != nil { - return fmt.Errorf("failed to decode reward account key hash: %w", err) + return fmt.Errorf("invalid operator key: %w", err) } - if len(hashBytes) != 28 { - return fmt.Errorf("invalid key hash length: expected 28, got %d", len(hashBytes)) + p.Operator = PoolKeyHash(Blake2b224(opBytes)) + } + + if aux.VrfKeyHash != "" { + vrfBytes, err := hex.DecodeString(aux.VrfKeyHash) + if err != nil { + return fmt.Errorf("invalid VRF key hash: %w", err) + } + p.VrfKeyHash = VrfKeyHash(Blake2b256(vrfBytes)) + } + + // Convert pool owners + if len(aux.PoolOwners) > 0 { + owners := make([]AddrKeyHash, len(aux.PoolOwners)) + for i, owner := range aux.PoolOwners { + ownerBytes, err := hex.DecodeString(owner) + if err != nil { + return fmt.Errorf("invalid pool owner key: %w", err) + } + owners[i] = AddrKeyHash(Blake2b224(ownerBytes)) } - var hash Blake2b224 - copy(hash[:], hashBytes) - p.RewardAccount = hash + p.PoolOwners = owners } return nil diff --git a/ledger/shelley/genesis_test.go b/ledger/shelley/genesis_test.go index 057d7186..ea26a05d 100644 --- a/ledger/shelley/genesis_test.go +++ b/ledger/shelley/genesis_test.go @@ -15,7 +15,6 @@ package shelley_test import ( - //"bytes" "encoding/hex" "encoding/json" "math/big" From a90c6a13ba89545881649ea5c26969d2485daf1b Mon Sep 17 00:00:00 2001 From: Jenita Date: Fri, 18 Jul 2025 18:17:34 -0500 Subject: [PATCH 07/16] feat: created genesis pools from shelly genesis Signed-off-by: Jenita --- ledger/common/certs.go | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/ledger/common/certs.go b/ledger/common/certs.go index 46bdc4fd..02e3d5e8 100644 --- a/ledger/common/certs.go +++ b/ledger/common/certs.go @@ -393,7 +393,6 @@ type PoolRegistrationCertificate struct { func (p *PoolRegistrationCertificate) UnmarshalJSON(data []byte) error { type Alias PoolRegistrationCertificate - // Temporary struct for initial unmarshaling aux := &struct { Operator string `json:"operator"` VrfKeyHash string `json:"vrfKeyHash"` @@ -413,7 +412,10 @@ func (p *PoolRegistrationCertificate) UnmarshalJSON(data []byte) error { return fmt.Errorf("failed to unmarshal pool registration: %w", err) } + p.Pledge = aux.Pledge p.Cost = aux.Cost + p.Relays = aux.Relays + p.PoolMetadata = aux.PoolMetadata // Handle margin field if len(aux.Margin) > 0 { @@ -438,20 +440,27 @@ func (p *PoolRegistrationCertificate) UnmarshalJSON(data []byte) error { // Handle reward account if len(aux.RewardAccount) > 0 { - var rewardAccount struct { - Credential struct { - KeyHash string `json:"key hash"` - } `json:"credential"` + type credential struct { + KeyHash string `json:"key hash"` } - if err := json.Unmarshal(aux.RewardAccount, &rewardAccount); err != nil { + type rewardAccount struct { + Credential credential `json:"credential"` + Network string `json:"network,omitempty"` + } + + var ra rewardAccount + if err := json.Unmarshal(aux.RewardAccount, &ra); err != nil { return fmt.Errorf("failed to unmarshal reward account: %w", err) } - if rewardAccount.Credential.KeyHash != "" { - hashBytes, err := hex.DecodeString(rewardAccount.Credential.KeyHash) + if ra.Credential.KeyHash != "" { + hashBytes, err := hex.DecodeString(ra.Credential.KeyHash) if err != nil { return fmt.Errorf("failed to decode reward account key hash: %w", err) } + if len(hashBytes) != 28 { + return fmt.Errorf("invalid key hash length: expected 28, got %d", len(hashBytes)) + } var hash Blake2b224 copy(hash[:], hashBytes) p.RewardAccount = AddrKeyHash(hash) From 6d696a7aaf734603a752d3eee8ffee9ff2a1da3c Mon Sep 17 00:00:00 2001 From: Jenita Date: Sat, 19 Jul 2025 19:45:10 -0500 Subject: [PATCH 08/16] feat: created genesis pools from shelly genesis Signed-off-by: Jenita --- ledger/common/certs.go | 41 ++++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/ledger/common/certs.go b/ledger/common/certs.go index 02e3d5e8..fee67b0f 100644 --- a/ledger/common/certs.go +++ b/ledger/common/certs.go @@ -391,9 +391,7 @@ type PoolRegistrationCertificate struct { } func (p *PoolRegistrationCertificate) UnmarshalJSON(data []byte) error { - type Alias PoolRegistrationCertificate - - aux := &struct { + type tempPool struct { Operator string `json:"operator"` VrfKeyHash string `json:"vrfKeyHash"` Pledge uint64 `json:"pledge"` @@ -403,24 +401,22 @@ func (p *PoolRegistrationCertificate) UnmarshalJSON(data []byte) error { PoolOwners []string `json:"poolOwners"` Relays []PoolRelay `json:"relays"` PoolMetadata *PoolMetadata `json:"poolMetadata,omitempty"` - *Alias - }{ - Alias: (*Alias)(p), } - if err := json.Unmarshal(data, aux); err != nil { + var tmp tempPool + if err := json.Unmarshal(data, &tmp); err != nil { return fmt.Errorf("failed to unmarshal pool registration: %w", err) } - p.Pledge = aux.Pledge - p.Cost = aux.Cost - p.Relays = aux.Relays - p.PoolMetadata = aux.PoolMetadata + p.Pledge = tmp.Pledge + p.Cost = tmp.Cost + p.Relays = tmp.Relays + p.PoolMetadata = tmp.PoolMetadata // Handle margin field - if len(aux.Margin) > 0 { + if len(tmp.Margin) > 0 { var marginValue interface{} - if err := json.Unmarshal(aux.Margin, &marginValue); err != nil { + if err := json.Unmarshal(tmp.Margin, &marginValue); err != nil { return fmt.Errorf("failed to unmarshal margin: %w", err) } @@ -439,17 +435,16 @@ func (p *PoolRegistrationCertificate) UnmarshalJSON(data []byte) error { } // Handle reward account - if len(aux.RewardAccount) > 0 { + if len(tmp.RewardAccount) > 0 { type credential struct { KeyHash string `json:"key hash"` } type rewardAccount struct { Credential credential `json:"credential"` - Network string `json:"network,omitempty"` } var ra rewardAccount - if err := json.Unmarshal(aux.RewardAccount, &ra); err != nil { + if err := json.Unmarshal(tmp.RewardAccount, &ra); err != nil { return fmt.Errorf("failed to unmarshal reward account: %w", err) } @@ -468,16 +463,16 @@ func (p *PoolRegistrationCertificate) UnmarshalJSON(data []byte) error { } // Convert string fields to binary types - if aux.Operator != "" { - opBytes, err := hex.DecodeString(aux.Operator) + if tmp.Operator != "" { + opBytes, err := hex.DecodeString(tmp.Operator) if err != nil { return fmt.Errorf("invalid operator key: %w", err) } p.Operator = PoolKeyHash(Blake2b224(opBytes)) } - if aux.VrfKeyHash != "" { - vrfBytes, err := hex.DecodeString(aux.VrfKeyHash) + if tmp.VrfKeyHash != "" { + vrfBytes, err := hex.DecodeString(tmp.VrfKeyHash) if err != nil { return fmt.Errorf("invalid VRF key hash: %w", err) } @@ -485,9 +480,9 @@ func (p *PoolRegistrationCertificate) UnmarshalJSON(data []byte) error { } // Convert pool owners - if len(aux.PoolOwners) > 0 { - owners := make([]AddrKeyHash, len(aux.PoolOwners)) - for i, owner := range aux.PoolOwners { + if len(tmp.PoolOwners) > 0 { + owners := make([]AddrKeyHash, len(tmp.PoolOwners)) + for i, owner := range tmp.PoolOwners { ownerBytes, err := hex.DecodeString(owner) if err != nil { return fmt.Errorf("invalid pool owner key: %w", err) From e484fdb60cd3e03760ebd86b66e7eef65a7b2c43 Mon Sep 17 00:00:00 2001 From: Jenita Date: Sat, 19 Jul 2025 20:29:49 -0500 Subject: [PATCH 09/16] feat: created genesis pools from shelly genesis Signed-off-by: Jenita --- ledger/common/certs.go | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/ledger/common/certs.go b/ledger/common/certs.go index fee67b0f..66d6abe7 100644 --- a/ledger/common/certs.go +++ b/ledger/common/certs.go @@ -302,11 +302,11 @@ const ( ) type PoolRelay struct { - Type int - Port *uint32 - Ipv4 *net.IP - Ipv6 *net.IP - Hostname *string + Type int `json:"type"` + Port *uint32 `json:"port,omitempty"` + Ipv4 *net.IP `json:"ipv4,omitempty"` + Ipv6 *net.IP `json:"ipv6,omitempty"` + Hostname *string `json:"hostname,omitempty"` } func (p *PoolRelay) UnmarshalCBOR(data []byte) error { @@ -391,6 +391,7 @@ type PoolRegistrationCertificate struct { } func (p *PoolRegistrationCertificate) UnmarshalJSON(data []byte) error { + //nolint:musttag type tempPool struct { Operator string `json:"operator"` VrfKeyHash string `json:"vrfKeyHash"` @@ -399,8 +400,15 @@ func (p *PoolRegistrationCertificate) UnmarshalJSON(data []byte) error { Margin json.RawMessage `json:"margin"` RewardAccount json.RawMessage `json:"rewardAccount"` PoolOwners []string `json:"poolOwners"` - Relays []PoolRelay `json:"relays"` - PoolMetadata *PoolMetadata `json:"poolMetadata,omitempty"` + Relays []struct { + Type int `json:"type"` + Port *uint32 `json:"port,omitempty"` + Ipv4 *net.IP `json:"ipv4,omitempty"` + Ipv6 *net.IP `json:"ipv6,omitempty"` + Hostname *string `json:"hostname,omitempty"` + } `json:"relays"` + + PoolMetadata *PoolMetadata `json:"poolMetadata,omitempty"` } var tmp tempPool @@ -410,7 +418,16 @@ func (p *PoolRegistrationCertificate) UnmarshalJSON(data []byte) error { p.Pledge = tmp.Pledge p.Cost = tmp.Cost - p.Relays = tmp.Relays + p.Relays = make([]PoolRelay, len(tmp.Relays)) + for i, relay := range tmp.Relays { + p.Relays[i] = PoolRelay{ + Type: relay.Type, + Port: relay.Port, + Ipv4: relay.Ipv4, + Ipv6: relay.Ipv6, + Hostname: relay.Hostname, + } + } p.PoolMetadata = tmp.PoolMetadata // Handle margin field From 2138a765ac1f72f0b766d398e135db3013358593 Mon Sep 17 00:00:00 2001 From: Jenita Date: Sun, 20 Jul 2025 21:06:25 -0500 Subject: [PATCH 10/16] feat: created genesis pools from shelly genesis Signed-off-by: Jenita --- ledger/common/certs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ledger/common/certs.go b/ledger/common/certs.go index 66d6abe7..63675489 100644 --- a/ledger/common/certs.go +++ b/ledger/common/certs.go @@ -391,8 +391,8 @@ type PoolRegistrationCertificate struct { } func (p *PoolRegistrationCertificate) UnmarshalJSON(data []byte) error { - //nolint:musttag type tempPool struct { + //nolint:musttag Operator string `json:"operator"` VrfKeyHash string `json:"vrfKeyHash"` Pledge uint64 `json:"pledge"` From 23c86c128d35f8b698b685403ae479301d042f09 Mon Sep 17 00:00:00 2001 From: Chris Gianelloni Date: Thu, 24 Jul 2025 16:20:08 -0400 Subject: [PATCH 11/16] ci(lint): move nolint:musttag Signed-off-by: Chris Gianelloni --- ledger/common/certs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ledger/common/certs.go b/ledger/common/certs.go index 63675489..5eadc6c3 100644 --- a/ledger/common/certs.go +++ b/ledger/common/certs.go @@ -392,7 +392,6 @@ type PoolRegistrationCertificate struct { func (p *PoolRegistrationCertificate) UnmarshalJSON(data []byte) error { type tempPool struct { - //nolint:musttag Operator string `json:"operator"` VrfKeyHash string `json:"vrfKeyHash"` Pledge uint64 `json:"pledge"` @@ -412,6 +411,7 @@ func (p *PoolRegistrationCertificate) UnmarshalJSON(data []byte) error { } var tmp tempPool + //nolint:musttag if err := json.Unmarshal(data, &tmp); err != nil { return fmt.Errorf("failed to unmarshal pool registration: %w", err) } From 2a5b082735b5613348e5c12ca749e016ae2a8fc5 Mon Sep 17 00:00:00 2001 From: Jenita Date: Mon, 28 Jul 2025 14:48:04 -0500 Subject: [PATCH 12/16] feat: created genesis pools from shelly genesis Signed-off-by: Jenita --- ledger/common/certs.go | 48 +++++++++------------------- ledger/shelley/genesis.go | 57 ++++++++++++++++++++++++++++++---- ledger/shelley/genesis_test.go | 15 ++++++--- 3 files changed, 76 insertions(+), 44 deletions(-) diff --git a/ledger/common/certs.go b/ledger/common/certs.go index 5eadc6c3..fb4d5f23 100644 --- a/ledger/common/certs.go +++ b/ledger/common/certs.go @@ -19,7 +19,6 @@ import ( "encoding/json" "errors" "fmt" - "math/big" "net" "github.com/blinklabs-io/gouroboros/cbor" @@ -383,7 +382,7 @@ type PoolRegistrationCertificate struct { VrfKeyHash VrfKeyHash `json:"vrfKeyHash"` Pledge uint64 `json:"pledge"` Cost uint64 `json:"cost"` - Margin cbor.Rat `json:"margin"` + Margin GenesisRat `json:"margin"` RewardAccount AddrKeyHash `json:"rewardAccount"` PoolOwners []AddrKeyHash `json:"poolOwners"` Relays []PoolRelay `json:"relays"` @@ -406,7 +405,6 @@ func (p *PoolRegistrationCertificate) UnmarshalJSON(data []byte) error { Ipv6 *net.IP `json:"ipv6,omitempty"` Hostname *string `json:"hostname,omitempty"` } `json:"relays"` - PoolMetadata *PoolMetadata `json:"poolMetadata,omitempty"` } @@ -432,35 +430,18 @@ func (p *PoolRegistrationCertificate) UnmarshalJSON(data []byte) error { // Handle margin field if len(tmp.Margin) > 0 { - var marginValue interface{} - if err := json.Unmarshal(tmp.Margin, &marginValue); err != nil { + if err := p.Margin.UnmarshalJSON(tmp.Margin); err != nil { return fmt.Errorf("failed to unmarshal margin: %w", err) } - - switch v := marginValue.(type) { - case float64: - p.Margin.Rat = new(big.Rat).SetFloat64(v) - case []interface{}: - if len(v) == 2 { - if num, ok := v[0].(float64); ok { - if den, ok := v[1].(float64); ok && den != 0 { - p.Margin.Rat = new(big.Rat).SetFrac64(int64(num), int64(den)) - } - } - } - } } // Handle reward account if len(tmp.RewardAccount) > 0 { - type credential struct { - KeyHash string `json:"key hash"` - } - type rewardAccount struct { - Credential credential `json:"credential"` + var ra struct { + Credential struct { + KeyHash string `json:"key hash"` + } `json:"credential"` } - - var ra rewardAccount if err := json.Unmarshal(tmp.RewardAccount, &ra); err != nil { return fmt.Errorf("failed to unmarshal reward account: %w", err) } @@ -470,30 +451,29 @@ func (p *PoolRegistrationCertificate) UnmarshalJSON(data []byte) error { if err != nil { return fmt.Errorf("failed to decode reward account key hash: %w", err) } - if len(hashBytes) != 28 { - return fmt.Errorf("invalid key hash length: expected 28, got %d", len(hashBytes)) + if len(hashBytes) != AddressHashSize { + return fmt.Errorf("invalid key hash length: expected %d, got %d", AddressHashSize, len(hashBytes)) } - var hash Blake2b224 - copy(hash[:], hashBytes) - p.RewardAccount = AddrKeyHash(hash) + p.RewardAccount = AddrKeyHash(NewBlake2b224(hashBytes)) } } - // Convert string fields to binary types + // Convert operator key if tmp.Operator != "" { opBytes, err := hex.DecodeString(tmp.Operator) if err != nil { return fmt.Errorf("invalid operator key: %w", err) } - p.Operator = PoolKeyHash(Blake2b224(opBytes)) + p.Operator = PoolKeyHash(NewBlake2b224(opBytes)) } + // Convert VRF key hash if tmp.VrfKeyHash != "" { vrfBytes, err := hex.DecodeString(tmp.VrfKeyHash) if err != nil { return fmt.Errorf("invalid VRF key hash: %w", err) } - p.VrfKeyHash = VrfKeyHash(Blake2b256(vrfBytes)) + p.VrfKeyHash = VrfKeyHash(NewBlake2b256(vrfBytes)) } // Convert pool owners @@ -504,7 +484,7 @@ func (p *PoolRegistrationCertificate) UnmarshalJSON(data []byte) error { if err != nil { return fmt.Errorf("invalid pool owner key: %w", err) } - owners[i] = AddrKeyHash(Blake2b224(ownerBytes)) + owners[i] = AddrKeyHash(NewBlake2b224(ownerBytes)) } p.PoolOwners = owners } diff --git a/ledger/shelley/genesis.go b/ledger/shelley/genesis.go index aa479ca5..7f7e4f25 100644 --- a/ledger/shelley/genesis.go +++ b/ledger/shelley/genesis.go @@ -22,12 +22,49 @@ import ( "math/big" "os" "reflect" + "sync" "time" "github.com/blinklabs-io/gouroboros/cbor" "github.com/blinklabs-io/gouroboros/ledger/common" ) +// Network stake address headers +var ( + stakeHeaderRegistry = map[string]struct { + Base, Script byte + }{ + "Mainnet": {0xE0, 0xE1}, + "Testnet": {0xF0, 0xF1}, + } + stakeHeaderMutex sync.RWMutex +) + +// RegisterStakeHeaders allows runtime registration of network address headers +func RegisterStakeHeaders(networkId string, baseHeader, scriptHeader byte) { + stakeHeaderMutex.Lock() + defer stakeHeaderMutex.Unlock() + stakeHeaderRegistry[networkId] = struct{ Base, Script byte }{ + Base: baseHeader, + Script: scriptHeader, + } +} + +func getStakeAddressHeader(networkId string, isScript bool) (byte, error) { + stakeHeaderMutex.RLock() + defer stakeHeaderMutex.RUnlock() + + headers, exists := stakeHeaderRegistry[networkId] + if !exists { + return 0, errors.New("network not registered in stake header registry") + } + + if isScript { + return headers.Script, nil + } + return headers.Base, nil +} + type ShelleyGenesis struct { cbor.StructAsArray SystemStart time.Time `json:"systemStart"` @@ -232,7 +269,6 @@ func (g *ShelleyGenesis) GenesisUtxos() ([]common.Utxo, error) { return ret, nil } -// InitialPools returns all pools and their delegators from the genesis data func (g *ShelleyGenesis) InitialPools() (map[string]common.PoolRegistrationCertificate, map[string][]common.Address, error) { pools := make(map[string]common.PoolRegistrationCertificate) poolStake := make(map[string][]common.Address) @@ -241,6 +277,11 @@ func (g *ShelleyGenesis) InitialPools() (map[string]common.PoolRegistrationCerti return pools, poolStake, nil } + headerByte, err := getStakeAddressHeader(g.NetworkId, true) + if err != nil { + return nil, nil, errors.New("failed to get stake address header") + } + for stakeAddr, poolId := range g.Staking.Stake { if len(stakeAddr) != 56 { return nil, nil, errors.New("invalid stake address length") @@ -251,7 +292,7 @@ func (g *ShelleyGenesis) InitialPools() (map[string]common.PoolRegistrationCerti return nil, nil, errors.New("failed to decode stake key") } - stakeAddrBytes := append([]byte{0xE1}, stakeKeyBytes...) + stakeAddrBytes := append([]byte{headerByte}, stakeKeyBytes...) addr, err := common.NewAddressFromBytes(stakeAddrBytes) if err != nil { return nil, nil, errors.New("failed to create stake address") @@ -286,7 +327,6 @@ func (g *ShelleyGenesis) InitialPools() (map[string]common.PoolRegistrationCerti return pools, poolStake, nil } -// PoolById returns a specific pool by its ID along with its delegators func (g *ShelleyGenesis) PoolById(poolId string) (*common.PoolRegistrationCertificate, []common.Address, error) { if len(poolId) != 56 { return nil, nil, errors.New("invalid pool ID length") @@ -297,9 +337,9 @@ func (g *ShelleyGenesis) PoolById(poolId string) (*common.PoolRegistrationCertif return nil, nil, errors.New("pool not found") } - operatorBytes, err := hex.DecodeString(poolId) + headerByte, err := getStakeAddressHeader(g.NetworkId, true) if err != nil { - return nil, nil, errors.New("failed to decode pool operator key") + return nil, nil, errors.New("failed to get stake address header") } var delegators []common.Address @@ -314,7 +354,7 @@ func (g *ShelleyGenesis) PoolById(poolId string) (*common.PoolRegistrationCertif return nil, nil, errors.New("failed to decode stake key") } - stakeAddrBytes := append([]byte{0xE1}, stakeKeyBytes...) + stakeAddrBytes := append([]byte{headerByte}, stakeKeyBytes...) addr, err := common.NewAddressFromBytes(stakeAddrBytes) if err != nil { return nil, nil, errors.New("failed to create stake address") @@ -324,6 +364,11 @@ func (g *ShelleyGenesis) PoolById(poolId string) (*common.PoolRegistrationCertif } } + operatorBytes, err := hex.DecodeString(poolId) + if err != nil { + return nil, nil, errors.New("failed to decode pool operator key") + } + return &common.PoolRegistrationCertificate{ Operator: common.Blake2b224(operatorBytes), VrfKeyHash: pool.VrfKeyHash, diff --git a/ledger/shelley/genesis_test.go b/ledger/shelley/genesis_test.go index ea26a05d..b5c2e174 100644 --- a/ledger/shelley/genesis_test.go +++ b/ledger/shelley/genesis_test.go @@ -27,6 +27,12 @@ import ( "github.com/blinklabs-io/gouroboros/ledger/shelley" ) +func init() { + // Initialize network headers for tests + shelley.RegisterStakeHeaders("Mainnet", 0xE0, 0xE1) + shelley.RegisterStakeHeaders("Testnet", 0xF0, 0xF1) +} + const shelleyGenesisConfig = ` { "activeSlotsCoeff": 0.05, @@ -256,6 +262,7 @@ func TestGenesisStaking(t *testing.T) { const testGenesis = `{ "systemStart": "2017-09-23T21:44:51Z", "networkMagic": 764824073, + "networkId": "Testnet", "staking": { "pools": { "0aedc455785463235311c990f68742c9043cd79af09ab31c2ba5e195": { @@ -327,8 +334,8 @@ func TestGenesisStaking(t *testing.T) { } else { // Extract stake key from address addrBytes, _ := delegs[0].Bytes() - if len(addrBytes) != 29 || addrBytes[0] != 0xE1 { - t.Error("Delegator address is not in expected stake address format") + if len(addrBytes) != 29 || addrBytes[0] != 0xF1 { // Testnet script stake address + t.Errorf("Delegator address is not in expected stake address format: got %x", addrBytes[0]) } else { stakeKey := hex.EncodeToString(addrBytes[1:]) if stakeKey != expectedStakeKey { @@ -364,8 +371,8 @@ func TestGenesisStaking(t *testing.T) { } else { // Extract stake key from address addrBytes, _ := delegators[0].Bytes() - if len(addrBytes) != 29 || addrBytes[0] != 0xE1 { - t.Error("Delegator address is not in expected stake address format") + if len(addrBytes) != 29 || addrBytes[0] != 0xF1 { // Testnet script stake address + t.Errorf("Delegator address is not in expected stake address format: got %x", addrBytes[0]) } else { stakeKey := hex.EncodeToString(addrBytes[1:]) if stakeKey != expectedStakeKey { From 61a22c85ef23c0a0d1ce0ab9309403ae47a5dd14 Mon Sep 17 00:00:00 2001 From: Jenita Date: Mon, 28 Jul 2025 17:49:03 -0500 Subject: [PATCH 13/16] feat: created genesis pools from shelly genesis Signed-off-by: Jenita --- ledger/common/address.go | 51 +++++++++++++----- ledger/shelley/genesis.go | 98 ++++++++++++---------------------- ledger/shelley/genesis_test.go | 75 +++++++++++++++++--------- 3 files changed, 121 insertions(+), 103 deletions(-) diff --git a/ledger/common/address.go b/ledger/common/address.go index 6d626f42..08fb3966 100644 --- a/ledger/common/address.go +++ b/ledger/common/address.go @@ -107,24 +107,47 @@ func NewAddressFromParts( paymentAddr []byte, stakingAddr []byte, ) (Address, error) { + // Validate network ID + if networkId != AddressNetworkTestnet && networkId != AddressNetworkMainnet { + return Address{}, errors.New("invalid network ID") + } + + // Handle stake-only addresses + if addrType == AddressTypeNoneKey || addrType == AddressTypeNoneScript { + if len(paymentAddr) > 0 { + return Address{}, errors.New("payment address must be empty for stake-only addresses") + } + if len(stakingAddr) != AddressHashSize { + return Address{}, fmt.Errorf("staking key must be exactly %d bytes", AddressHashSize) + } + + if addrType == AddressTypeNoneScript && networkId == AddressNetworkTestnet { + header := byte(0xF1) + addrBytes := append([]byte{header}, stakingAddr...) + return NewAddressFromBytes(addrBytes) + } + + header := addrType<<4 | networkId + addrBytes := append([]byte{header}, stakingAddr...) + return NewAddressFromBytes(addrBytes) + } + + // Handle regular addresses if len(paymentAddr) != AddressHashSize { - return Address{}, fmt.Errorf( - "invalid payment address hash length: %d", - len(paymentAddr), - ) + return Address{}, fmt.Errorf("payment address must be exactly %d bytes", AddressHashSize) } + if len(stakingAddr) > 0 && len(stakingAddr) != AddressHashSize { - return Address{}, fmt.Errorf( - "invalid staking address hash length: %d", - len(stakingAddr), - ) + return Address{}, fmt.Errorf("staking address must be empty or exactly %d bytes", AddressHashSize) } - return Address{ - addressType: addrType, - networkId: networkId, - paymentAddress: paymentAddr[:], - stakingAddress: stakingAddr[:], - }, nil + + header := addrType<<4 | networkId + addrBytes := append([]byte{header}, paymentAddr...) + if len(stakingAddr) > 0 { + addrBytes = append(addrBytes, stakingAddr...) + } + + return NewAddressFromBytes(addrBytes) } func NewByronAddressFromParts( diff --git a/ledger/shelley/genesis.go b/ledger/shelley/genesis.go index 7f7e4f25..dd3bb644 100644 --- a/ledger/shelley/genesis.go +++ b/ledger/shelley/genesis.go @@ -22,49 +22,12 @@ import ( "math/big" "os" "reflect" - "sync" "time" "github.com/blinklabs-io/gouroboros/cbor" "github.com/blinklabs-io/gouroboros/ledger/common" ) -// Network stake address headers -var ( - stakeHeaderRegistry = map[string]struct { - Base, Script byte - }{ - "Mainnet": {0xE0, 0xE1}, - "Testnet": {0xF0, 0xF1}, - } - stakeHeaderMutex sync.RWMutex -) - -// RegisterStakeHeaders allows runtime registration of network address headers -func RegisterStakeHeaders(networkId string, baseHeader, scriptHeader byte) { - stakeHeaderMutex.Lock() - defer stakeHeaderMutex.Unlock() - stakeHeaderRegistry[networkId] = struct{ Base, Script byte }{ - Base: baseHeader, - Script: scriptHeader, - } -} - -func getStakeAddressHeader(networkId string, isScript bool) (byte, error) { - stakeHeaderMutex.RLock() - defer stakeHeaderMutex.RUnlock() - - headers, exists := stakeHeaderRegistry[networkId] - if !exists { - return 0, errors.New("network not registered in stake header registry") - } - - if isScript { - return headers.Script, nil - } - return headers.Base, nil -} - type ShelleyGenesis struct { cbor.StructAsArray SystemStart time.Time `json:"systemStart"` @@ -269,6 +232,17 @@ func (g *ShelleyGenesis) GenesisUtxos() ([]common.Utxo, error) { return ret, nil } +func (g *ShelleyGenesis) getNetworkId() (uint8, error) { + switch g.NetworkId { + case "Mainnet": + return common.AddressNetworkMainnet, nil + case "Testnet": + return common.AddressNetworkTestnet, nil + default: + return 0, errors.New("unknown network ID") + } +} + func (g *ShelleyGenesis) InitialPools() (map[string]common.PoolRegistrationCertificate, map[string][]common.Address, error) { pools := make(map[string]common.PoolRegistrationCertificate) poolStake := make(map[string][]common.Address) @@ -277,38 +251,36 @@ func (g *ShelleyGenesis) InitialPools() (map[string]common.PoolRegistrationCerti return pools, poolStake, nil } - headerByte, err := getStakeAddressHeader(g.NetworkId, true) + networkId, err := g.getNetworkId() if err != nil { - return nil, nil, errors.New("failed to get stake address header") + return nil, nil, err } + // Process all stake addresses for stakeAddr, poolId := range g.Staking.Stake { - if len(stakeAddr) != 56 { - return nil, nil, errors.New("invalid stake address length") - } - - stakeKeyBytes, err := hex.DecodeString(stakeAddr) + stakeKey, err := hex.DecodeString(stakeAddr) if err != nil { return nil, nil, errors.New("failed to decode stake key") } - stakeAddrBytes := append([]byte{headerByte}, stakeKeyBytes...) - addr, err := common.NewAddressFromBytes(stakeAddrBytes) + addr, err := common.NewAddressFromParts( + common.AddressTypeNoneScript, // Script stake address + networkId, + nil, + stakeKey, + ) if err != nil { - return nil, nil, errors.New("failed to create stake address") + return nil, nil, errors.New("failed to create address") } poolStake[poolId] = append(poolStake[poolId], addr) } + // Process all stake pools for poolId, pool := range g.Staking.Pools { - if len(poolId) != 56 { - return nil, nil, errors.New("invalid pool ID length") - } - operatorBytes, err := hex.DecodeString(poolId) if err != nil { - return nil, nil, errors.New("failed to decode pool operator key") + return nil, nil, errors.New("failed to decode pool ID") } pools[poolId] = common.PoolRegistrationCertificate{ @@ -334,30 +306,30 @@ func (g *ShelleyGenesis) PoolById(poolId string) (*common.PoolRegistrationCertif pool, exists := g.Staking.Pools[poolId] if !exists { - return nil, nil, errors.New("pool not found") + return nil, nil, errors.New("pool not found") } - headerByte, err := getStakeAddressHeader(g.NetworkId, true) + networkId, err := g.getNetworkId() if err != nil { - return nil, nil, errors.New("failed to get stake address header") + return nil, nil, err } var delegators []common.Address for stakeAddr, pId := range g.Staking.Stake { if pId == poolId { - if len(stakeAddr) != 56 { - return nil, nil, errors.New("invalid stake address length") - } - - stakeKeyBytes, err := hex.DecodeString(stakeAddr) + stakeKey, err := hex.DecodeString(stakeAddr) if err != nil { return nil, nil, errors.New("failed to decode stake key") } - stakeAddrBytes := append([]byte{headerByte}, stakeKeyBytes...) - addr, err := common.NewAddressFromBytes(stakeAddrBytes) + addr, err := common.NewAddressFromParts( + common.AddressTypeNoneScript, + networkId, + nil, + stakeKey, + ) if err != nil { - return nil, nil, errors.New("failed to create stake address") + return nil, nil, errors.New("failed to create address") } delegators = append(delegators, addr) diff --git a/ledger/shelley/genesis_test.go b/ledger/shelley/genesis_test.go index b5c2e174..c2784e98 100644 --- a/ledger/shelley/genesis_test.go +++ b/ledger/shelley/genesis_test.go @@ -27,12 +27,6 @@ import ( "github.com/blinklabs-io/gouroboros/ledger/shelley" ) -func init() { - // Initialize network headers for tests - shelley.RegisterStakeHeaders("Mainnet", 0xE0, 0xE1) - shelley.RegisterStakeHeaders("Testnet", 0xF0, 0xF1) -} - const shelleyGenesisConfig = ` { "activeSlotsCoeff": 0.05, @@ -103,6 +97,9 @@ const shelleyGenesisConfig = ` "securityParam": 2160 } ` +const ( + expectedTestnetScriptStakeHeader = 0xF1 +) var expectedGenesisObj = shelley.ShelleyGenesis{ SystemStart: time.Date( @@ -332,16 +329,29 @@ func TestGenesisStaking(t *testing.T) { if len(delegs) != 1 { t.Errorf("Expected 1 delegator, got %d", len(delegs)) } else { - // Extract stake key from address - addrBytes, _ := delegs[0].Bytes() - if len(addrBytes) != 29 || addrBytes[0] != 0xF1 { // Testnet script stake address - t.Errorf("Delegator address is not in expected stake address format: got %x", addrBytes[0]) - } else { - stakeKey := hex.EncodeToString(addrBytes[1:]) - if stakeKey != expectedStakeKey { - t.Errorf("Delegator key mismatch:\nExpected: %s\nActual: %s", - expectedStakeKey, stakeKey) - } + // Verify address format + addrBytes, err := delegs[0].Bytes() + if err != nil { + t.Fatalf("Failed to get address bytes: %v", err) + } + + // Should be 29 bytes (1 header + 28 stake key) + if len(addrBytes) != 29 { + t.Errorf("Expected address length 29, got %d", len(addrBytes)) + } + + // Verify testnet script stake address header + // In TestInitialPools and TestPoolById, replace the header check with: + if addrBytes[0] != expectedTestnetScriptStakeHeader { + t.Errorf("Expected header byte %x, got %x", + expectedTestnetScriptStakeHeader, addrBytes[0]) + } + + // Verify stake key matches + stakeKey := hex.EncodeToString(addrBytes[1:]) + if stakeKey != expectedStakeKey { + t.Errorf("Delegator key mismatch:\nExpected: %s\nActual: %s", + expectedStakeKey, stakeKey) } } }) @@ -369,16 +379,29 @@ func TestGenesisStaking(t *testing.T) { if len(delegators) != 1 { t.Errorf("Expected 1 delegator, got %d", len(delegators)) } else { - // Extract stake key from address - addrBytes, _ := delegators[0].Bytes() - if len(addrBytes) != 29 || addrBytes[0] != 0xF1 { // Testnet script stake address - t.Errorf("Delegator address is not in expected stake address format: got %x", addrBytes[0]) - } else { - stakeKey := hex.EncodeToString(addrBytes[1:]) - if stakeKey != expectedStakeKey { - t.Errorf("Delegator key mismatch:\nExpected: %s\nActual: %s", - expectedStakeKey, stakeKey) - } + // Verify address format + addrBytes, err := delegators[0].Bytes() + if err != nil { + t.Fatalf("Failed to get address bytes: %v", err) + } + + // Should be 29 bytes (1 header + 28 stake key) + if len(addrBytes) != 29 { + t.Errorf("Expected address length 29, got %d", len(addrBytes)) + } + + // Verify testnet script stake address header + // In TestInitialPools and TestPoolById, replace the header check with: + if addrBytes[0] != expectedTestnetScriptStakeHeader { + t.Errorf("Expected header byte %x, got %x", + expectedTestnetScriptStakeHeader, addrBytes[0]) + } + + // Verify stake key matches + stakeKey := hex.EncodeToString(addrBytes[1:]) + if stakeKey != expectedStakeKey { + t.Errorf("Delegator key mismatch:\nExpected: %s\nActual: %s", + expectedStakeKey, stakeKey) } } From ed3850a9a235581bc0b3defdccfc9bc577d61aee Mon Sep 17 00:00:00 2001 From: Jenita Date: Tue, 29 Jul 2025 18:03:45 -0500 Subject: [PATCH 14/16] feat: created genesis pools from shelly genesis Signed-off-by: Jenita --- ledger/common/address.go | 70 +++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/ledger/common/address.go b/ledger/common/address.go index 08fb3966..079189c2 100644 --- a/ledger/common/address.go +++ b/ledger/common/address.go @@ -120,16 +120,11 @@ func NewAddressFromParts( if len(stakingAddr) != AddressHashSize { return Address{}, fmt.Errorf("staking key must be exactly %d bytes", AddressHashSize) } - - if addrType == AddressTypeNoneScript && networkId == AddressNetworkTestnet { - header := byte(0xF1) - addrBytes := append([]byte{header}, stakingAddr...) - return NewAddressFromBytes(addrBytes) - } - - header := addrType<<4 | networkId - addrBytes := append([]byte{header}, stakingAddr...) - return NewAddressFromBytes(addrBytes) + return Address{ + addressType: addrType, + networkId: networkId, + stakingAddress: stakingAddr, + }, nil } // Handle regular addresses @@ -141,13 +136,12 @@ func NewAddressFromParts( return Address{}, fmt.Errorf("staking address must be empty or exactly %d bytes", AddressHashSize) } - header := addrType<<4 | networkId - addrBytes := append([]byte{header}, paymentAddr...) - if len(stakingAddr) > 0 { - addrBytes = append(addrBytes, stakingAddr...) - } - - return NewAddressFromBytes(addrBytes) + return Address{ + addressType: addrType, + networkId: networkId, + paymentAddress: paymentAddr, + stakingAddress: stakingAddr, + }, nil } func NewByronAddressFromParts( @@ -433,23 +427,33 @@ func (a Address) Bytes() ([]byte, error) { }, crc32.ChecksumIEEE(rawPayload), } - ret, err := cbor.Encode(tmpData) - if err != nil { - return nil, fmt.Errorf( - "failed to encode Byron address data: %w", - err, - ) + return cbor.Encode(tmpData) + } + + // Calculate header byte + header := (a.addressType << 4) | (a.networkId & AddressHeaderNetworkMask) + + if a.addressType == AddressTypeNoneScript && a.networkId == AddressNetworkTestnet { + header = (AddressTypeNoneScript << 4) | 1 + } + + // Build address bytes + ret := []byte{header} + if a.addressType != AddressTypeNoneKey && a.addressType != AddressTypeNoneScript { + if len(a.paymentAddress) != AddressHashSize { + return nil, fmt.Errorf("invalid payment address length: %d (expected %d)", + len(a.paymentAddress), AddressHashSize) } - return ret, nil - } - ret := []byte{} - ret = append( - ret, - (a.addressType<<4)|(a.networkId&AddressHeaderNetworkMask), - ) - ret = append(ret, a.paymentAddress...) - ret = append(ret, a.stakingAddress...) - ret = append(ret, a.extraData...) + ret = append(ret, a.paymentAddress...) + } + + if len(a.stakingAddress) > 0 { + if len(a.stakingAddress) != AddressHashSize { + return nil, fmt.Errorf("invalid staking address length: %d (expected %d)", + len(a.stakingAddress), AddressHashSize) + } + ret = append(ret, a.stakingAddress...) + } return ret, nil } From f18d211500f527464ea3c28398f44248710a8038 Mon Sep 17 00:00:00 2001 From: Jenita Date: Tue, 29 Jul 2025 18:13:09 -0500 Subject: [PATCH 15/16] feat: created genesis pools from shelly genesis Signed-off-by: Jenita --- ledger/common/address.go | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/ledger/common/address.go b/ledger/common/address.go index 079189c2..579fb4ee 100644 --- a/ledger/common/address.go +++ b/ledger/common/address.go @@ -427,33 +427,31 @@ func (a Address) Bytes() ([]byte, error) { }, crc32.ChecksumIEEE(rawPayload), } - return cbor.Encode(tmpData) + ret, err := cbor.Encode(tmpData) + if err != nil { + return nil, fmt.Errorf( + "failed to encode Byron address data: %w", + err, + ) + } + return ret, nil } - // Calculate header byte header := (a.addressType << 4) | (a.networkId & AddressHeaderNetworkMask) if a.addressType == AddressTypeNoneScript && a.networkId == AddressNetworkTestnet { header = (AddressTypeNoneScript << 4) | 1 } - // Build address bytes - ret := []byte{header} - if a.addressType != AddressTypeNoneKey && a.addressType != AddressTypeNoneScript { - if len(a.paymentAddress) != AddressHashSize { - return nil, fmt.Errorf("invalid payment address length: %d (expected %d)", - len(a.paymentAddress), AddressHashSize) - } - ret = append(ret, a.paymentAddress...) - } + ret := []byte{} + ret = append( + ret, + header, + ) + ret = append(ret, a.paymentAddress...) + ret = append(ret, a.stakingAddress...) + ret = append(ret, a.extraData...) - if len(a.stakingAddress) > 0 { - if len(a.stakingAddress) != AddressHashSize { - return nil, fmt.Errorf("invalid staking address length: %d (expected %d)", - len(a.stakingAddress), AddressHashSize) - } - ret = append(ret, a.stakingAddress...) - } return ret, nil } From 492b205751871ed4c0e8573d7571dd9d236ee538 Mon Sep 17 00:00:00 2001 From: Jenita Date: Wed, 30 Jul 2025 15:27:51 -0500 Subject: [PATCH 16/16] feat: created genesis pools from shelly genesis Signed-off-by: Jenita --- ledger/common/address.go | 10 +------ ledger/shelley/genesis_test.go | 49 +++++++++++----------------------- 2 files changed, 17 insertions(+), 42 deletions(-) diff --git a/ledger/common/address.go b/ledger/common/address.go index 579fb4ee..3c66cca5 100644 --- a/ledger/common/address.go +++ b/ledger/common/address.go @@ -436,22 +436,14 @@ func (a Address) Bytes() ([]byte, error) { } return ret, nil } - - header := (a.addressType << 4) | (a.networkId & AddressHeaderNetworkMask) - - if a.addressType == AddressTypeNoneScript && a.networkId == AddressNetworkTestnet { - header = (AddressTypeNoneScript << 4) | 1 - } - ret := []byte{} ret = append( ret, - header, + (a.addressType<<4)|(a.networkId&AddressHeaderNetworkMask), ) ret = append(ret, a.paymentAddress...) ret = append(ret, a.stakingAddress...) ret = append(ret, a.extraData...) - return ret, nil } diff --git a/ledger/shelley/genesis_test.go b/ledger/shelley/genesis_test.go index c2784e98..05dfa588 100644 --- a/ledger/shelley/genesis_test.go +++ b/ledger/shelley/genesis_test.go @@ -330,28 +330,20 @@ func TestGenesisStaking(t *testing.T) { t.Errorf("Expected 1 delegator, got %d", len(delegs)) } else { // Verify address format - addrBytes, err := delegs[0].Bytes() - if err != nil { - t.Fatalf("Failed to get address bytes: %v", err) + // Verify address type and network + if delegs[0].NetworkId() != common.AddressNetworkTestnet { + t.Errorf("Expected testnet address, got network ID %d", delegs[0].NetworkId()) } - // Should be 29 bytes (1 header + 28 stake key) - if len(addrBytes) != 29 { - t.Errorf("Expected address length 29, got %d", len(addrBytes)) - } - - // Verify testnet script stake address header - // In TestInitialPools and TestPoolById, replace the header check with: - if addrBytes[0] != expectedTestnetScriptStakeHeader { - t.Errorf("Expected header byte %x, got %x", - expectedTestnetScriptStakeHeader, addrBytes[0]) + if delegs[0].Type() != common.AddressTypeNoneScript { + t.Errorf("Expected script stake address type, got %d", delegs[0].Type()) } // Verify stake key matches - stakeKey := hex.EncodeToString(addrBytes[1:]) - if stakeKey != expectedStakeKey { + stakeKeyHash := delegs[0].StakeKeyHash() + if hex.EncodeToString(stakeKeyHash[:]) != expectedStakeKey { t.Errorf("Delegator key mismatch:\nExpected: %s\nActual: %s", - expectedStakeKey, stakeKey) + expectedStakeKey, hex.EncodeToString(stakeKeyHash[:])) } } }) @@ -379,29 +371,20 @@ func TestGenesisStaking(t *testing.T) { if len(delegators) != 1 { t.Errorf("Expected 1 delegator, got %d", len(delegators)) } else { - // Verify address format - addrBytes, err := delegators[0].Bytes() - if err != nil { - t.Fatalf("Failed to get address bytes: %v", err) - } - - // Should be 29 bytes (1 header + 28 stake key) - if len(addrBytes) != 29 { - t.Errorf("Expected address length 29, got %d", len(addrBytes)) + // Verify address type and network + if delegators[0].NetworkId() != common.AddressNetworkTestnet { + t.Errorf("Expected testnet address, got network ID %d", delegators[0].NetworkId()) } - // Verify testnet script stake address header - // In TestInitialPools and TestPoolById, replace the header check with: - if addrBytes[0] != expectedTestnetScriptStakeHeader { - t.Errorf("Expected header byte %x, got %x", - expectedTestnetScriptStakeHeader, addrBytes[0]) + if delegators[0].Type() != common.AddressTypeNoneScript { + t.Errorf("Expected script stake address type, got %d", delegators[0].Type()) } // Verify stake key matches - stakeKey := hex.EncodeToString(addrBytes[1:]) - if stakeKey != expectedStakeKey { + stakeKeyHash := delegators[0].StakeKeyHash() + if hex.EncodeToString(stakeKeyHash[:]) != expectedStakeKey { t.Errorf("Delegator key mismatch:\nExpected: %s\nActual: %s", - expectedStakeKey, stakeKey) + expectedStakeKey, hex.EncodeToString(stakeKeyHash[:])) } }