From 99a9a5b5b19182b38779bfac87792943e4e2075e Mon Sep 17 00:00:00 2001 From: Aurora Gaffney Date: Fri, 13 Oct 2023 20:59:41 -0500 Subject: [PATCH] feat: CBOR for block transactions --- cbor/cbor.go | 9 +++++++-- ledger/allegra.go | 26 ++++++++++++++++++++++++++ ledger/alonzo.go | 39 +++++++++++++++++++++++++++++++++------ ledger/babbage.go | 39 +++++++++++++++++++++++++++++++++------ ledger/mary.go | 26 ++++++++++++++++++++++++++ ledger/shelley.go | 31 +++++++++++++++++++++++++++++++ 6 files changed, 156 insertions(+), 14 deletions(-) diff --git a/cbor/cbor.go b/cbor/cbor.go index 786b9069..045317c9 100644 --- a/cbor/cbor.go +++ b/cbor/cbor.go @@ -53,12 +53,18 @@ type StructAsArray struct { type DecodeStoreCborInterface interface { Cbor() []byte + SetCbor([]byte) } type DecodeStoreCbor struct { cborData []byte } +func (d *DecodeStoreCbor) SetCbor(cborData []byte) { + d.cborData = make([]byte, len(cborData)) + copy(d.cborData, cborData) +} + // Cbor returns the original CBOR for the object func (d *DecodeStoreCbor) Cbor() []byte { return d.cborData @@ -72,7 +78,6 @@ func (d *DecodeStoreCbor) UnmarshalCbor(cborData []byte, dest DecodeStoreCborInt // Store a copy of the original CBOR data // This must be done after we copy from the temp object above, or it gets wiped out // when using struct embedding and the DecodeStoreCbor struct is embedded at a deeper level - d.cborData = make([]byte, len(cborData)) - copy(d.cborData, cborData) + d.SetCbor(cborData) return nil } diff --git a/ledger/allegra.go b/ledger/allegra.go index 0f1b46fe..547ac023 100644 --- a/ledger/allegra.go +++ b/ledger/allegra.go @@ -113,6 +113,32 @@ func (t AllegraTransaction) Metadata() *cbor.Value { return t.TxMetadata } +func (t *AllegraTransaction) Cbor() []byte { + // Return stored CBOR if we have any + cborData := t.DecodeStoreCbor.Cbor() + if cborData != nil { + return cborData[:] + } + // Return immediately if the body CBOR is also empty, which implies an empty TX object + if t.Body.Cbor() == nil { + return nil + } + // Generate our own CBOR + // This is necessary when a transaction is put together from pieces stored separately in a block + tmpObj := []any{ + cbor.RawMessage(t.Body.Cbor()), + cbor.RawMessage(t.WitnessSet.Cbor()), + } + if t.TxMetadata != nil { + tmpObj = append(tmpObj, cbor.RawMessage(t.TxMetadata.Cbor())) + } else { + tmpObj = append(tmpObj, nil) + } + // This should never fail, since we're only encoding a list and a bool value + cborData, _ = cbor.Encode(&tmpObj) + return cborData +} + func NewAllegraBlockFromCbor(data []byte) (*AllegraBlock, error) { var allegraBlock AllegraBlock if _, err := cbor.Decode(data, &allegraBlock); err != nil { diff --git a/ledger/alonzo.go b/ledger/alonzo.go index 58b9ed1c..20c70e34 100644 --- a/ledger/alonzo.go +++ b/ledger/alonzo.go @@ -114,7 +114,7 @@ func (b *AlonzoTransactionBody) Outputs() []TransactionOutput { type AlonzoTransactionOutput struct { cbor.StructAsArray - cborData []byte + cbor.DecodeStoreCbor OutputAddress Address OutputAmount MaryTransactionOutputValue TxOutputDatumHash *Blake2b256 @@ -122,7 +122,7 @@ type AlonzoTransactionOutput struct { func (o *AlonzoTransactionOutput) UnmarshalCBOR(cborData []byte) error { // Save original CBOR - o.cborData = cborData[:] + o.SetCbor(cborData) // Try to parse as Mary output first var tmpOutput MaryTransactionOutput if _, err := cbor.Decode(cborData, &tmpOutput); err == nil { @@ -152,10 +152,6 @@ func (o AlonzoTransactionOutput) MarshalJSON() ([]byte, error) { return json.Marshal(&tmpObj) } -func (o *AlonzoTransactionOutput) Cbor() []byte { - return o.cborData -} - func (o AlonzoTransactionOutput) Address() Address { return o.OutputAddress } @@ -183,6 +179,10 @@ type AlonzoTransactionWitnessSet struct { Redeemers []cbor.RawMessage `cbor:"5,keyasint,omitempty"` } +func (t *AlonzoTransactionWitnessSet) UnmarshalCBOR(cborData []byte) error { + return t.UnmarshalCbor(cborData, t) +} + type AlonzoTransaction struct { cbor.StructAsArray cbor.DecodeStoreCbor @@ -208,6 +208,33 @@ func (t AlonzoTransaction) Metadata() *cbor.Value { return t.TxMetadata } +func (t *AlonzoTransaction) Cbor() []byte { + // Return stored CBOR if we have any + cborData := t.DecodeStoreCbor.Cbor() + if cborData != nil { + return cborData[:] + } + // Return immediately if the body CBOR is also empty, which implies an empty TX object + if t.Body.Cbor() == nil { + return nil + } + // Generate our own CBOR + // This is necessary when a transaction is put together from pieces stored separately in a block + tmpObj := []any{ + cbor.RawMessage(t.Body.Cbor()), + cbor.RawMessage(t.WitnessSet.Cbor()), + t.IsValid, + } + if t.TxMetadata != nil { + tmpObj = append(tmpObj, cbor.RawMessage(t.TxMetadata.Cbor())) + } else { + tmpObj = append(tmpObj, nil) + } + // This should never fail, since we're only encoding a list and a bool value + cborData, _ = cbor.Encode(&tmpObj) + return cborData +} + func NewAlonzoBlockFromCbor(data []byte) (*AlonzoBlock, error) { var alonzoBlock AlonzoBlock if _, err := cbor.Decode(data, &alonzoBlock); err != nil { diff --git a/ledger/babbage.go b/ledger/babbage.go index 875dad4e..5ef4bc0d 100644 --- a/ledger/babbage.go +++ b/ledger/babbage.go @@ -215,7 +215,7 @@ func (d *BabbageTransactionOutputDatumOption) MarshalCBOR() ([]byte, error) { } type BabbageTransactionOutput struct { - cborData []byte + cbor.DecodeStoreCbor OutputAddress Address `cbor:"0,keyasint,omitempty"` OutputAmount MaryTransactionOutputValue `cbor:"1,keyasint,omitempty"` DatumOption *BabbageTransactionOutputDatumOption `cbor:"2,keyasint,omitempty"` @@ -225,7 +225,7 @@ type BabbageTransactionOutput struct { func (o *BabbageTransactionOutput) UnmarshalCBOR(cborData []byte) error { // Save original CBOR - o.cborData = cborData[:] + o.SetCbor(cborData) // Try to parse as legacy output first var tmpOutput AlonzoTransactionOutput if _, err := cbor.Decode(cborData, &tmpOutput); err == nil { @@ -262,10 +262,6 @@ func (o BabbageTransactionOutput) MarshalJSON() ([]byte, error) { return json.Marshal(&tmpObj) } -func (o *BabbageTransactionOutput) Cbor() []byte { - return o.cborData -} - func (o BabbageTransactionOutput) Address() Address { return o.OutputAddress } @@ -297,6 +293,10 @@ type BabbageTransactionWitnessSet struct { PlutusV2Scripts []cbor.RawMessage `cbor:"6,keyasint,omitempty"` } +func (t *BabbageTransactionWitnessSet) UnmarshalCBOR(cborData []byte) error { + return t.UnmarshalCbor(cborData, t) +} + type BabbageTransaction struct { cbor.StructAsArray cbor.DecodeStoreCbor @@ -322,6 +322,33 @@ func (t BabbageTransaction) Metadata() *cbor.Value { return t.TxMetadata } +func (t *BabbageTransaction) Cbor() []byte { + // Return stored CBOR if we have any + cborData := t.DecodeStoreCbor.Cbor() + if cborData != nil { + return cborData[:] + } + // Return immediately if the body CBOR is also empty, which implies an empty TX object + if t.Body.Cbor() == nil { + return nil + } + // Generate our own CBOR + // This is necessary when a transaction is put together from pieces stored separately in a block + tmpObj := []any{ + cbor.RawMessage(t.Body.Cbor()), + cbor.RawMessage(t.WitnessSet.Cbor()), + t.IsValid, + } + if t.TxMetadata != nil { + tmpObj = append(tmpObj, cbor.RawMessage(t.TxMetadata.Cbor())) + } else { + tmpObj = append(tmpObj, nil) + } + // This should never fail, since we're only encoding a list and a bool value + cborData, _ = cbor.Encode(&tmpObj) + return cborData +} + func NewBabbageBlockFromCbor(data []byte) (*BabbageBlock, error) { var babbageBlock BabbageBlock if _, err := cbor.Decode(data, &babbageBlock); err != nil { diff --git a/ledger/mary.go b/ledger/mary.go index 4c71b452..91ee5224 100644 --- a/ledger/mary.go +++ b/ledger/mary.go @@ -124,6 +124,32 @@ func (t MaryTransaction) Metadata() *cbor.Value { return t.TxMetadata } +func (t *MaryTransaction) Cbor() []byte { + // Return stored CBOR if we have any + cborData := t.DecodeStoreCbor.Cbor() + if cborData != nil { + return cborData[:] + } + // Return immediately if the body CBOR is also empty, which implies an empty TX object + if t.Body.Cbor() == nil { + return nil + } + // Generate our own CBOR + // This is necessary when a transaction is put together from pieces stored separately in a block + tmpObj := []any{ + cbor.RawMessage(t.Body.Cbor()), + cbor.RawMessage(t.WitnessSet.Cbor()), + } + if t.TxMetadata != nil { + tmpObj = append(tmpObj, cbor.RawMessage(t.TxMetadata.Cbor())) + } else { + tmpObj = append(tmpObj, nil) + } + // This should never fail, since we're only encoding a list and a bool value + cborData, _ = cbor.Encode(&tmpObj) + return cborData +} + type MaryTransactionOutput struct { cbor.StructAsArray cbor.DecodeStoreCbor diff --git a/ledger/shelley.go b/ledger/shelley.go index 59496c89..d126104b 100644 --- a/ledger/shelley.go +++ b/ledger/shelley.go @@ -219,11 +219,16 @@ func (o ShelleyTransactionOutput) Datum() *cbor.LazyValue { } type ShelleyTransactionWitnessSet struct { + cbor.DecodeStoreCbor VkeyWitnesses []interface{} `cbor:"0,keyasint,omitempty"` MultisigScripts []interface{} `cbor:"1,keyasint,omitempty"` BootstrapWitnesses []interface{} `cbor:"2,keyasint,omitempty"` } +func (t *ShelleyTransactionWitnessSet) UnmarshalCBOR(cborData []byte) error { + return t.UnmarshalCbor(cborData, t) +} + type ShelleyTransaction struct { cbor.StructAsArray cbor.DecodeStoreCbor @@ -248,6 +253,32 @@ func (t ShelleyTransaction) Metadata() *cbor.Value { return t.TxMetadata } +func (t *ShelleyTransaction) Cbor() []byte { + // Return stored CBOR if we have any + cborData := t.DecodeStoreCbor.Cbor() + if cborData != nil { + return cborData[:] + } + // Return immediately if the body CBOR is also empty, which implies an empty TX object + if t.Body.Cbor() == nil { + return nil + } + // Generate our own CBOR + // This is necessary when a transaction is put together from pieces stored separately in a block + tmpObj := []any{ + cbor.RawMessage(t.Body.Cbor()), + cbor.RawMessage(t.WitnessSet.Cbor()), + } + if t.TxMetadata != nil { + tmpObj = append(tmpObj, cbor.RawMessage(t.TxMetadata.Cbor())) + } else { + tmpObj = append(tmpObj, nil) + } + // This should never fail, since we're only encoding a list and a bool value + cborData, _ = cbor.Encode(&tmpObj) + return cborData +} + func NewShelleyBlockFromCbor(data []byte) (*ShelleyBlock, error) { var shelleyBlock ShelleyBlock if _, err := cbor.Decode(data, &shelleyBlock); err != nil {