diff --git a/ledger/allegra.go b/ledger/allegra.go index 141ad1ee..6e4a2f3c 100644 --- a/ledger/allegra.go +++ b/ledger/allegra.go @@ -37,6 +37,7 @@ const ( // Allegra functions var ( NewAllegraBlockFromCbor = allegra.NewAllegraBlockFromCbor + NewAllegraBlockHeaderFromCbor = allegra.NewAllegraBlockHeaderFromCbor NewAllegraTransactionFromCbor = allegra.NewAllegraTransactionFromCbor NewAllegraTransactionBodyFromCbor = allegra.NewAllegraTransactionBodyFromCbor ) diff --git a/ledger/allegra/allegra.go b/ledger/allegra/allegra.go index 2438cf50..203b3bd9 100644 --- a/ledger/allegra/allegra.go +++ b/ledger/allegra/allegra.go @@ -329,6 +329,14 @@ func NewAllegraBlockFromCbor(data []byte) (*AllegraBlock, error) { return &allegraBlock, nil } +func NewAllegraBlockHeaderFromCbor(data []byte) (*AllegraBlockHeader, error) { + var allegraBlockHeader AllegraBlockHeader + if _, err := cbor.Decode(data, &allegraBlockHeader); err != nil { + return nil, fmt.Errorf("Allegra block header decode error: %w", err) + } + return &allegraBlockHeader, nil +} + func NewAllegraTransactionBodyFromCbor( data []byte, ) (*AllegraTransactionBody, error) { diff --git a/ledger/alonzo.go b/ledger/alonzo.go index 6a06f1bb..0b5b34cc 100644 --- a/ledger/alonzo.go +++ b/ledger/alonzo.go @@ -40,6 +40,7 @@ const ( // Alonzo functions var ( NewAlonzoBlockFromCbor = alonzo.NewAlonzoBlockFromCbor + NewAlonzoBlockHeaderFromCbor = alonzo.NewAlonzoBlockHeaderFromCbor NewAlonzoTransactionFromCbor = alonzo.NewAlonzoTransactionFromCbor NewAlonzoTransactionBodyFromCbor = alonzo.NewAlonzoTransactionBodyFromCbor NewAlonzoTransactionOutputFromCbor = alonzo.NewAlonzoTransactionOutputFromCbor diff --git a/ledger/alonzo/alonzo.go b/ledger/alonzo/alonzo.go index 00343acc..87663cd5 100644 --- a/ledger/alonzo/alonzo.go +++ b/ledger/alonzo/alonzo.go @@ -532,6 +532,14 @@ func NewAlonzoBlockFromCbor(data []byte) (*AlonzoBlock, error) { return &alonzoBlock, nil } +func NewAlonzoBlockHeaderFromCbor(data []byte) (*AlonzoBlockHeader, error) { + var alonzoBlockHeader AlonzoBlockHeader + if _, err := cbor.Decode(data, &alonzoBlockHeader); err != nil { + return nil, fmt.Errorf("Alonzo block header decode error: %w", err) + } + return &alonzoBlockHeader, nil +} + func NewAlonzoTransactionBodyFromCbor( data []byte, ) (*AlonzoTransactionBody, error) { diff --git a/ledger/babbage/babbage.go b/ledger/babbage/babbage.go index 90d00d6b..bc8658a1 100644 --- a/ledger/babbage/babbage.go +++ b/ledger/babbage/babbage.go @@ -145,33 +145,39 @@ func (b *BabbageBlock) Utxorpc() *utxorpc.Block { type BabbageBlockHeader struct { cbor.StructAsArray cbor.DecodeStoreCbor - hash string - Body struct { - cbor.StructAsArray - BlockNumber uint64 - Slot uint64 - PrevHash common.Blake2b256 - IssuerVkey common.IssuerVkey - VrfKey []byte - VrfResult common.VrfResult - BlockBodySize uint64 - BlockBodyHash common.Blake2b256 - OpCert struct { - cbor.StructAsArray - HotVkey []byte - SequenceNumber uint32 - KesPeriod uint32 - Signature []byte - } - ProtoVersion struct { - cbor.StructAsArray - Major uint64 - Minor uint64 - } - } + hash string + Body BabbageBlockHeaderBody Signature []byte } +type BabbageBlockHeaderBody struct { + cbor.StructAsArray + BlockNumber uint64 + Slot uint64 + PrevHash common.Blake2b256 + IssuerVkey common.IssuerVkey + VrfKey []byte + VrfResult common.VrfResult + BlockBodySize uint64 + BlockBodyHash common.Blake2b256 + OpCert BabbageOpCert + ProtoVersion BabbageProtoVersion +} + +type BabbageOpCert struct { + cbor.StructAsArray + HotVkey []byte + SequenceNumber uint32 + KesPeriod uint32 + Signature []byte +} + +type BabbageProtoVersion struct { + cbor.StructAsArray + Major uint64 + Minor uint64 +} + func (h *BabbageBlockHeader) UnmarshalCBOR(cborData []byte) error { return h.UnmarshalCbor(cborData, h) } diff --git a/ledger/block.go b/ledger/block.go index 0bee4623..045bfdb6 100644 --- a/ledger/block.go +++ b/ledger/block.go @@ -53,13 +53,21 @@ func NewBlockHeaderFromCbor(blockType uint, data []byte) (BlockHeader, error) { return NewByronEpochBoundaryBlockHeaderFromCbor(data) case BlockTypeByronMain: return NewByronMainBlockHeaderFromCbor(data) - // TODO: break into separate cases and parse as specific block header types (#844) - case BlockTypeShelley, BlockTypeAllegra, BlockTypeMary, BlockTypeAlonzo: + case BlockTypeShelley: return NewShelleyBlockHeaderFromCbor(data) - case BlockTypeBabbage, BlockTypeConway: + case BlockTypeAllegra: + return NewAllegraBlockHeaderFromCbor(data) + case BlockTypeMary: + return NewMaryBlockHeaderFromCbor(data) + case BlockTypeAlonzo: + return NewAlonzoBlockHeaderFromCbor(data) + case BlockTypeBabbage: return NewBabbageBlockHeaderFromCbor(data) + case BlockTypeConway: + return NewConwayBlockHeaderFromCbor(data) + default: + return nil, fmt.Errorf("unknown node-to-node block type: %d", blockType) } - return nil, fmt.Errorf("unknown node-to-node block type: %d", blockType) } func DetermineBlockType(data []byte) (uint, error) { diff --git a/ledger/block_test.go b/ledger/block_test.go new file mode 100644 index 00000000..40fa8d21 --- /dev/null +++ b/ledger/block_test.go @@ -0,0 +1,142 @@ +package ledger + +import ( + "fmt" + "testing" + + "github.com/blinklabs-io/gouroboros/ledger/allegra" + "github.com/blinklabs-io/gouroboros/ledger/babbage" + "github.com/blinklabs-io/gouroboros/ledger/common" + "github.com/blinklabs-io/gouroboros/ledger/mary" + "github.com/blinklabs-io/gouroboros/ledger/shelley" + "github.com/fxamacker/cbor/v2" +) + +func mockShelleyCBOR() []byte { + shelleyHeader := shelley.ShelleyBlockHeader{ + Body: shelley.ShelleyBlockHeaderBody{ + BlockNumber: 12345, + Slot: 67890, + PrevHash: common.Blake2b256{}, + IssuerVkey: common.IssuerVkey{}, + VrfKey: []byte{0x01, 0x02}, + NonceVrf: common.VrfResult{}, + LeaderVrf: common.VrfResult{}, + BlockBodySize: 512, + BlockBodyHash: common.Blake2b256{}, + OpCertHotVkey: []byte{0x03, 0x04}, + OpCertSequenceNumber: 10, + OpCertKesPeriod: 20, + OpCertSignature: []byte{0x05, 0x06}, + ProtoMajorVersion: 1, + ProtoMinorVersion: 0, + }, + Signature: []byte{0x07, 0x08}, + } + + // Convert to CBOR + data, err := cbor.Marshal(shelleyHeader) + if err != nil { + fmt.Printf("CBOR Encoding Error: %v\n", err) + } + return data +} + +func mockAllegraCBOR() []byte { + allegraHeader := allegra.AllegraBlockHeader{ShelleyBlockHeader: ShelleyBlockHeader{}} + data, _ := cbor.Marshal(allegraHeader) + return data +} + +func mockMaryCBOR() []byte { + maryHeader := mary.MaryBlockHeader{ShelleyBlockHeader: ShelleyBlockHeader{}} + data, _ := cbor.Marshal(maryHeader) + return data +} + +func mockAlonzoCBOR() []byte { + alonzoHeader := AlonzoBlockHeader{ShelleyBlockHeader: ShelleyBlockHeader{}} + data, _ := cbor.Marshal(alonzoHeader) + return data +} + +func mockBabbageCBOR() []byte { + babbageHeader := babbage.BabbageBlockHeader{ + Body: babbage.BabbageBlockHeaderBody{ + BlockNumber: 54321, + Slot: 98765, + PrevHash: common.Blake2b256{}, + IssuerVkey: common.IssuerVkey{}, + VrfKey: []byte{0x09, 0x10}, + VrfResult: common.VrfResult{}, + BlockBodySize: 1024, + BlockBodyHash: common.Blake2b256{}, + OpCert: babbage.BabbageOpCert{ + HotVkey: []byte{0x11, 0x12}, + SequenceNumber: 30, + KesPeriod: 40, + Signature: []byte{0x13, 0x14}, + }, + ProtoVersion: babbage.BabbageProtoVersion{ + Major: 2, + Minor: 0, + }, + }, + Signature: []byte{0x15, 0x16}, + } + + // Convert to CBOR + data, err := cbor.Marshal(babbageHeader) + if err != nil { + fmt.Printf("CBOR Encoding Error for Babbage: %v\n", err) + } + return data +} + +func mockConwayCBOR() []byte { + conwayHeader := ConwayBlockHeader{BabbageBlockHeader: BabbageBlockHeader{}} + data, _ := cbor.Marshal(conwayHeader) + return data +} + +func TestNewBlockHeaderFromCbor(t *testing.T) { + tests := []struct { + name string + blockType uint + data []byte + expectErr bool + expectedFn string + }{ + {"Shelley Block", BlockTypeShelley, mockShelleyCBOR(), false, "NewShelleyBlockHeaderFromCbor"}, + {"Allegra Block", BlockTypeAllegra, mockAllegraCBOR(), false, "NewAllegraBlockHeaderFromCbor"}, + {"Mary Block", BlockTypeMary, mockMaryCBOR(), false, "NewMaryBlockHeaderFromCbor"}, + {"Alonzo Block", BlockTypeAlonzo, mockAlonzoCBOR(), false, "NewAlonzoBlockHeaderFromCbor"}, + {"Babbage Block", BlockTypeBabbage, mockBabbageCBOR(), false, "NewBabbageBlockHeaderFromCbor"}, + {"Conway Block", BlockTypeConway, mockConwayCBOR(), false, "NewConwayBlockHeaderFromCbor"}, + {"Invalid Block Type", 9999, []byte{0xFF, 0x00, 0x00}, true, "UnknownFunction"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fmt.Printf("\n Running Test: %s\n", test.name) + + header, err := NewBlockHeaderFromCbor(test.blockType, test.data) + + if test.expectErr { + if err == nil { + t.Errorf("Expected error for %s, but got none!", test.name) + } else { + fmt.Printf("Expected failure for %s: %v\n", test.name, err) + } + } else { + if err != nil { + t.Errorf("Unexpected error for %s: %v", test.name, err) + } else if header == nil { + t.Errorf("Expected non-nil block header for %s, but got nil", test.name) + } else { + fmt.Printf("Test Passed: %s → %s executed successfully!\n", test.name, test.expectedFn) + } + } + }) + } +} diff --git a/ledger/mary.go b/ledger/mary.go index 58c5b216..1d539f8a 100644 --- a/ledger/mary.go +++ b/ledger/mary.go @@ -40,6 +40,7 @@ const ( // Mary functions var ( NewMaryBlockFromCbor = mary.NewMaryBlockFromCbor + NewMaryBlockHeaderFromCbor = mary.NewMaryBlockHeaderFromCbor NewMaryTransactionFromCbor = mary.NewMaryTransactionFromCbor NewMaryTransactionBodyFromCbor = mary.NewMaryTransactionBodyFromCbor NewMaryTransactionOutputFromCbor = mary.NewMaryTransactionOutputFromCbor diff --git a/ledger/mary/mary.go b/ledger/mary/mary.go index fc63c41e..31df2d82 100644 --- a/ledger/mary/mary.go +++ b/ledger/mary/mary.go @@ -414,6 +414,14 @@ func NewMaryBlockFromCbor(data []byte) (*MaryBlock, error) { return &maryBlock, nil } +func NewMaryBlockHeaderFromCbor(data []byte) (*MaryBlockHeader, error) { + var maryBlockHeader MaryBlockHeader + if _, err := cbor.Decode(data, &maryBlockHeader); err != nil { + return nil, fmt.Errorf("Mary block header decode error: %w", err) + } + return &maryBlockHeader, nil +} + func NewMaryTransactionBodyFromCbor(data []byte) (*MaryTransactionBody, error) { var maryTx MaryTransactionBody if _, err := cbor.Decode(data, &maryTx); err != nil { diff --git a/ledger/shelley/shelley.go b/ledger/shelley/shelley.go index e15da7b7..751b0abf 100644 --- a/ledger/shelley/shelley.go +++ b/ledger/shelley/shelley.go @@ -135,27 +135,28 @@ func (b *ShelleyBlock) Utxorpc() *utxorpc.Block { type ShelleyBlockHeader struct { cbor.StructAsArray cbor.DecodeStoreCbor - hash string - Body struct { - cbor.StructAsArray - BlockNumber uint64 - Slot uint64 - PrevHash common.Blake2b256 - IssuerVkey common.IssuerVkey - VrfKey []byte - NonceVrf common.VrfResult - LeaderVrf common.VrfResult - BlockBodySize uint64 - BlockBodyHash common.Blake2b256 - OpCertHotVkey []byte - OpCertSequenceNumber uint32 - OpCertKesPeriod uint32 - OpCertSignature []byte - ProtoMajorVersion uint64 - ProtoMinorVersion uint64 - } + hash string + Body ShelleyBlockHeaderBody Signature []byte } +type ShelleyBlockHeaderBody struct { + cbor.StructAsArray + BlockNumber uint64 + Slot uint64 + PrevHash common.Blake2b256 + IssuerVkey common.IssuerVkey + VrfKey []byte + NonceVrf common.VrfResult + LeaderVrf common.VrfResult + BlockBodySize uint64 + BlockBodyHash common.Blake2b256 + OpCertHotVkey []byte + OpCertSequenceNumber uint32 + OpCertKesPeriod uint32 + OpCertSignature []byte + ProtoMajorVersion uint64 + ProtoMinorVersion uint64 +} func (h *ShelleyBlockHeader) UnmarshalCBOR(cborData []byte) error { return h.UnmarshalCbor(cborData, h)