From fb23e2a27943c6d6dac7615c0a16b3f9e918edd9 Mon Sep 17 00:00:00 2001 From: Aurora Gaffney Date: Thu, 3 Jul 2025 13:39:57 -0400 Subject: [PATCH] feat: support for decoding script ref Fixes #1052 Signed-off-by: Aurora Gaffney --- ledger/alonzo/alonzo.go | 2 +- ledger/babbage/babbage.go | 19 +++-- ledger/byron/byron.go | 2 +- ledger/common/{native_script.go => script.go} | 72 +++++++++++++++++++ ledger/common/script_test.go | 38 ++++++++++ ledger/common/tx.go | 2 +- ledger/mary/mary.go | 2 +- ledger/shelley/shelley.go | 2 +- 8 files changed, 123 insertions(+), 16 deletions(-) rename ledger/common/{native_script.go => script.go} (56%) create mode 100644 ledger/common/script_test.go diff --git a/ledger/alonzo/alonzo.go b/ledger/alonzo/alonzo.go index c83c7104..f7d67f39 100644 --- a/ledger/alonzo/alonzo.go +++ b/ledger/alonzo/alonzo.go @@ -324,7 +324,7 @@ func (o AlonzoTransactionOutput) Address() common.Address { return o.OutputAddress } -func (o AlonzoTransactionOutput) ScriptRef() *cbor.LazyValue { +func (o AlonzoTransactionOutput) ScriptRef() common.Script { return nil } diff --git a/ledger/babbage/babbage.go b/ledger/babbage/babbage.go index 0af3d3a1..a7d834c3 100644 --- a/ledger/babbage/babbage.go +++ b/ledger/babbage/babbage.go @@ -420,11 +420,11 @@ func (d *BabbageTransactionOutputDatumOption) MarshalCBOR() ([]byte, error) { type BabbageTransactionOutput struct { cbor.DecodeStoreCbor - OutputAddress common.Address `cbor:"0,keyasint,omitempty"` - OutputAmount mary.MaryTransactionOutputValue `cbor:"1,keyasint,omitempty"` - DatumOption *BabbageTransactionOutputDatumOption `cbor:"2,keyasint,omitempty"` - TxScriptRef *cbor.Tag `cbor:"3,keyasint,omitempty"` - legacyOutput bool + OutputAddress common.Address `cbor:"0,keyasint,omitempty"` + OutputAmount mary.MaryTransactionOutputValue `cbor:"1,keyasint,omitempty"` + DatumOption *BabbageTransactionOutputDatumOption `cbor:"2,keyasint,omitempty"` + TxOutScriptRef *common.ScriptRef `cbor:"3,keyasint,omitempty"` + legacyOutput bool } func (o *BabbageTransactionOutput) UnmarshalCBOR(cborData []byte) error { @@ -486,14 +486,11 @@ func (o BabbageTransactionOutput) Address() common.Address { return o.OutputAddress } -func (o BabbageTransactionOutput) ScriptRef() *cbor.LazyValue { - if o.TxScriptRef == nil { +func (o BabbageTransactionOutput) ScriptRef() common.Script { + if o.TxOutScriptRef == nil { return nil } - if lazyVal, ok := o.TxScriptRef.Content.(*cbor.LazyValue); ok { - return lazyVal - } - return nil + return o.TxOutScriptRef.Script } func (o BabbageTransactionOutput) Amount() uint64 { diff --git a/ledger/byron/byron.go b/ledger/byron/byron.go index 65756231..7d341ba8 100644 --- a/ledger/byron/byron.go +++ b/ledger/byron/byron.go @@ -414,7 +414,7 @@ func (o ByronTransactionOutput) Address() common.Address { return o.OutputAddress } -func (o ByronTransactionOutput) ScriptRef() *cbor.LazyValue { +func (o ByronTransactionOutput) ScriptRef() common.Script { return nil } diff --git a/ledger/common/native_script.go b/ledger/common/script.go similarity index 56% rename from ledger/common/native_script.go rename to ledger/common/script.go index db547e37..604c158c 100644 --- a/ledger/common/native_script.go +++ b/ledger/common/script.go @@ -15,15 +15,87 @@ package common import ( + "errors" "fmt" "github.com/blinklabs-io/gouroboros/cbor" ) +const ( + ScriptRefTypeNativeScript = 0 + ScriptRefTypePlutusV1 = 1 + ScriptRefTypePlutusV2 = 2 + ScriptRefTypePlutusV3 = 3 +) + +type Script interface { + isScript() +} + +type ScriptRef struct { + Type uint + Script Script +} + +func (s *ScriptRef) UnmarshalCBOR(data []byte) error { + // Unwrap outer CBOR tag + var tmpTag cbor.Tag + if _, err := cbor.Decode(data, &tmpTag); err != nil { + return err + } + innerCbor, ok := tmpTag.Content.([]byte) + if !ok { + return errors.New("unexpected tag type") + } + // Determine script type + var rawScript struct { + cbor.StructAsArray + Type uint + Raw cbor.RawMessage + } + if _, err := cbor.Decode(innerCbor, &rawScript); err != nil { + return err + } + var tmpScript Script + switch rawScript.Type { + case ScriptRefTypeNativeScript: + tmpScript = &NativeScript{} + case ScriptRefTypePlutusV1: + tmpScript = &PlutusV1Script{} + case ScriptRefTypePlutusV2: + tmpScript = &PlutusV2Script{} + case ScriptRefTypePlutusV3: + tmpScript = &PlutusV3Script{} + default: + return fmt.Errorf("unknown script type %d", rawScript.Type) + } + // Decode script + if _, err := cbor.Decode(rawScript.Raw, tmpScript); err != nil { + return err + } + s.Type = rawScript.Type + s.Script = tmpScript + return nil +} + +type PlutusV1Script []byte + +func (PlutusV1Script) isScript() {} + +type PlutusV2Script []byte + +func (PlutusV2Script) isScript() {} + +type PlutusV3Script []byte + +func (PlutusV3Script) isScript() {} + type NativeScript struct { item any } +func (NativeScript) isScript() {} + func (n *NativeScript) Item() any { return n.item } diff --git a/ledger/common/script_test.go b/ledger/common/script_test.go new file mode 100644 index 00000000..54eebb5f --- /dev/null +++ b/ledger/common/script_test.go @@ -0,0 +1,38 @@ +// Copyright 2025 Blink Labs Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common_test + +import ( + "encoding/hex" + "reflect" + "testing" + + "github.com/blinklabs-io/gouroboros/cbor" + "github.com/blinklabs-io/gouroboros/ledger/common" +) + +func TestScriptRefDecode(t *testing.T) { + // 24_0(<<[3, h'480123456789abcdef']>>) + testCbor, _ := hex.DecodeString("d8184c820349480123456789abcdef") + scriptCbor, _ := hex.DecodeString("480123456789abcdef") + expectedScript := common.PlutusV3Script(scriptCbor) + var testScriptRef common.ScriptRef + if _, err := cbor.Decode(testCbor, &testScriptRef); err != nil { + t.Fatalf("unexpected error decoding script ref CBOR: %s", err) + } + if !reflect.DeepEqual(testScriptRef.Script, &expectedScript) { + t.Fatalf("did not get expected script\n got: %#v\n wanted: %#v", testScriptRef.Script, &expectedScript) + } +} diff --git a/ledger/common/tx.go b/ledger/common/tx.go index 388317dd..aed0f838 100644 --- a/ledger/common/tx.go +++ b/ledger/common/tx.go @@ -71,7 +71,7 @@ type TransactionOutput interface { DatumHash() *Blake2b256 Cbor() []byte Utxorpc() (*utxorpc.TxOutput, error) - ScriptRef() *cbor.LazyValue + ScriptRef() Script } type TransactionWitnessSet interface { diff --git a/ledger/mary/mary.go b/ledger/mary/mary.go index 99b6c389..d4884286 100644 --- a/ledger/mary/mary.go +++ b/ledger/mary/mary.go @@ -446,7 +446,7 @@ func (o MaryTransactionOutput) Address() common.Address { return o.OutputAddress } -func (txo MaryTransactionOutput) ScriptRef() *cbor.LazyValue { +func (txo MaryTransactionOutput) ScriptRef() common.Script { return nil } diff --git a/ledger/shelley/shelley.go b/ledger/shelley/shelley.go index 858f5e11..0981ec52 100644 --- a/ledger/shelley/shelley.go +++ b/ledger/shelley/shelley.go @@ -388,7 +388,7 @@ func (o ShelleyTransactionOutput) Address() common.Address { return o.OutputAddress } -func (o ShelleyTransactionOutput) ScriptRef() *cbor.LazyValue { +func (o ShelleyTransactionOutput) ScriptRef() common.Script { return nil }