Skip to content

Commit dcee7ee

Browse files
authored
feat: evaluate PlutusV3 scripts on TX validation (#916)
Fixes #867 Signed-off-by: Aurora Gaffney <[email protected]>
1 parent 8cf773d commit dcee7ee

File tree

3 files changed

+130
-42
lines changed

3 files changed

+130
-42
lines changed

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ require (
99
connectrpc.com/connect v1.18.1
1010
connectrpc.com/grpchealth v1.4.0
1111
connectrpc.com/grpcreflect v1.3.0
12-
github.com/blinklabs-io/gouroboros v0.134.2
12+
github.com/blinklabs-io/gouroboros v0.135.1
1313
github.com/blinklabs-io/ouroboros-mock v0.3.8
14+
github.com/blinklabs-io/plutigo v0.0.12
1415
github.com/dgraph-io/badger/v4 v4.8.0
1516
github.com/glebarez/sqlite v1.11.0
1617
github.com/kelseyhightower/envconfig v1.4.0
@@ -50,7 +51,6 @@ require (
5051
github.com/andybalholm/brotli v1.1.1 // indirect
5152
github.com/beorn7/perks v1.0.1 // indirect
5253
github.com/bits-and-blooms/bitset v1.20.0 // indirect
53-
github.com/blinklabs-io/plutigo v0.0.11 // indirect
5454
github.com/btcsuite/btcd/btcec/v2 v2.3.5 // indirect
5555
github.com/btcsuite/btcd/btcutil v1.1.6 // indirect
5656
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
4747
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
4848
github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU=
4949
github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
50-
github.com/blinklabs-io/gouroboros v0.134.2 h1:ADkkZTYOvocbjO6IL6c/4I+ZTX6/LDLBuPTLiDQ/2KE=
51-
github.com/blinklabs-io/gouroboros v0.134.2/go.mod h1:N4smrM9nmuSkYV92D7IL4z00/iAkbXysT1Ld7saVWeI=
50+
github.com/blinklabs-io/gouroboros v0.135.1 h1:2Z+GkULXKMFb/D1x7EE/LkCesAt/MQxrf3JNffBWvF4=
51+
github.com/blinklabs-io/gouroboros v0.135.1/go.mod h1:eCXFpt2fOhp2Id/0AqMaI/qr0bodbS0SIAOpNZVu9Iw=
5252
github.com/blinklabs-io/ouroboros-mock v0.3.8 h1:+DAt2rx0ouZUxee5DBMgZq3I1+ZdxFSHG9g3tYl/FKU=
5353
github.com/blinklabs-io/ouroboros-mock v0.3.8/go.mod h1:UwQIf4KqZwO13P9d90fbi3UL/X7JaJfeEbqk+bEeFQA=
54-
github.com/blinklabs-io/plutigo v0.0.11 h1:6a4EURP+Gq5jhP5N/C4lTxPFAdR6wF89pDiKvOV2OLg=
55-
github.com/blinklabs-io/plutigo v0.0.11/go.mod h1:L639Q8i2cSRuBhjgCHttPR0nnYwwsYVT4Btz7KpQjSw=
54+
github.com/blinklabs-io/plutigo v0.0.12 h1:DxnYKvKYn9OlgiTJi5Ofd97znSJWUtVZjZFqxW9HW/A=
55+
github.com/blinklabs-io/plutigo v0.0.12/go.mod h1:L639Q8i2cSRuBhjgCHttPR0nnYwwsYVT4Btz7KpQjSw=
5656
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
5757
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
5858
github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A=

ledger/eras/conway.go

Lines changed: 124 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
lcommon "github.com/blinklabs-io/gouroboros/ledger/common"
2828
"github.com/blinklabs-io/gouroboros/ledger/common/script"
2929
"github.com/blinklabs-io/gouroboros/ledger/conway"
30+
"github.com/blinklabs-io/plutigo/data"
3031
)
3132

3233
var ConwayEraDesc = EraDesc{
@@ -160,14 +161,133 @@ func ValidateTxConway(
160161
ls lcommon.LedgerState,
161162
pp lcommon.ProtocolParameters,
162163
) error {
164+
// Validate TX through ledger validation rules
163165
errs := []error{}
166+
var err error
164167
for _, validationFunc := range conway.UtxoValidationRules {
165-
errs = append(
166-
errs,
167-
validationFunc(tx, slot, ls, pp),
168+
err = validationFunc(tx, slot, ls, pp)
169+
if err != nil {
170+
errs = append(
171+
errs,
172+
err,
173+
)
174+
}
175+
}
176+
if len(errs) > 0 {
177+
return errors.Join(errs...)
178+
}
179+
// Skip script evaluation if TX is marked as not valid
180+
if !tx.IsValid() {
181+
return nil
182+
}
183+
// Resolve inputs
184+
resolvedInputs := []lcommon.Utxo{}
185+
for _, tmpInput := range tx.Inputs() {
186+
tmpUtxo, err := ls.UtxoById(tmpInput)
187+
if err != nil {
188+
return err
189+
}
190+
resolvedInputs = append(
191+
resolvedInputs,
192+
tmpUtxo,
193+
)
194+
}
195+
// Resolve reference inputs
196+
resolvedRefInputs := []lcommon.Utxo{}
197+
for _, tmpRefInput := range tx.ReferenceInputs() {
198+
tmpUtxo, err := ls.UtxoById(tmpRefInput)
199+
if err != nil {
200+
return err
201+
}
202+
resolvedRefInputs = append(
203+
resolvedRefInputs,
204+
tmpUtxo,
168205
)
169206
}
170-
return errors.Join(errs...)
207+
// Build TX script map
208+
scripts := make(map[lcommon.ScriptHash]lcommon.Script)
209+
for _, refInput := range resolvedRefInputs {
210+
tmpScript := refInput.Output.ScriptRef()
211+
if tmpScript == nil {
212+
continue
213+
}
214+
scripts[tmpScript.Hash()] = tmpScript
215+
}
216+
for _, tmpScript := range tx.Witnesses().PlutusV1Scripts() {
217+
scripts[tmpScript.Hash()] = tmpScript
218+
}
219+
for _, tmpScript := range tx.Witnesses().PlutusV2Scripts() {
220+
scripts[tmpScript.Hash()] = tmpScript
221+
}
222+
for _, tmpScript := range tx.Witnesses().PlutusV3Scripts() {
223+
scripts[tmpScript.Hash()] = tmpScript
224+
}
225+
// Evaluate scripts
226+
var txInfoV3 script.TxInfo
227+
txInfoV3, err = script.NewTxInfoV3FromTransaction(ls, tx, slices.Concat(resolvedInputs, resolvedRefInputs))
228+
if err != nil {
229+
return err
230+
}
231+
for _, redeemerPair := range txInfoV3.(script.TxInfoV3).Redeemers {
232+
purpose := redeemerPair.Key
233+
redeemer := redeemerPair.Value
234+
// Lookup script from redeemer purpose
235+
tmpScript := scripts[purpose.ScriptHash()]
236+
if tmpScript == nil {
237+
return fmt.Errorf("could not find script with hash %s", purpose.ScriptHash().String())
238+
}
239+
switch s := tmpScript.(type) {
240+
case *lcommon.PlutusV3Script:
241+
sc := script.NewScriptContextV3(txInfoV3, redeemer, purpose)
242+
// Round-trip the script context through CBOR
243+
// This is a temporary hack to work around a bug in plutigo
244+
scCbor, err := data.Encode(sc.ToPlutusData())
245+
if err != nil {
246+
return err
247+
}
248+
scNew, err := data.Decode(scCbor)
249+
if err != nil {
250+
return err
251+
}
252+
_, err = s.Evaluate(
253+
scNew,
254+
redeemer.ExUnits,
255+
)
256+
if err != nil {
257+
/*
258+
fmt.Printf("TX ID: %s\n", tx.Hash().String())
259+
fmt.Printf("purpose = %#v, redeemer = %#v\n", purpose, redeemer)
260+
scriptHash := s.Hash()
261+
fmt.Printf("scriptHash = %s\n", scriptHash.String())
262+
fmt.Printf("tx = %x\n", tx.Cbor())
263+
// Build inputs/outputs strings that can be plugged into Aiken script_context tests for comparison
264+
var tmpInputs []lcommon.TransactionInput
265+
var tmpOutputs []lcommon.TransactionOutput
266+
for _, input := range slices.Concat(resolvedInputs, resolvedRefInputs) {
267+
tmpInputs = append(tmpInputs, input.Id)
268+
tmpOutputs = append(tmpOutputs, input.Output)
269+
}
270+
tmpInputsCbor, err2 := cbor.Encode(tmpInputs)
271+
if err2 != nil {
272+
return err2
273+
}
274+
fmt.Printf("tmpInputs = %x\n", tmpInputsCbor)
275+
tmpOutputsCbor, err2 := cbor.Encode(tmpOutputs)
276+
if err2 != nil {
277+
return err2
278+
}
279+
fmt.Printf("tmpOutputs = %x\n", tmpOutputsCbor)
280+
scCbor, err2 := data.Encode(sc.ToPlutusData())
281+
if err2 != nil {
282+
return err2
283+
}
284+
fmt.Printf("scCbor = %x\n", scCbor)
285+
*/
286+
return err
287+
}
288+
}
289+
}
290+
return nil
171291
}
172292

173293
func EvaluateTxConway(
@@ -263,38 +383,6 @@ func EvaluateTxConway(
263383
Tag: redeemer.Tag,
264384
Index: redeemer.Index,
265385
}] = usedBudget
266-
/*
267-
if err != nil && !strings.Contains(err.Error(), "unimplemented") {
268-
fmt.Printf("TX ID: %s\n", tx.Hash().String())
269-
fmt.Printf("purpose = %#v, redeemer = %#v\n", purpose, redeemer)
270-
scriptHash := s.Hash()
271-
fmt.Printf("scriptHash = %s\n", scriptHash.String())
272-
fmt.Printf("tx = %x\n", tx.Cbor())
273-
// Build inputs/outputs strings that can be plugged into Aiken script_context tests for comparison
274-
var tmpInputs []lcommon.TransactionInput
275-
var tmpOutputs []lcommon.TransactionOutput
276-
for _, input := range slices.Concat(resolvedInputs, resolvedRefInputs) {
277-
tmpInputs = append(tmpInputs, input.Id)
278-
tmpOutputs = append(tmpOutputs, input.Output)
279-
}
280-
tmpInputsCbor, err := cbor.Encode(tmpInputs)
281-
if err != nil {
282-
return 0, lcommon.ExUnits{}, nil, err
283-
}
284-
fmt.Printf("tmpInputs = %x\n", tmpInputsCbor)
285-
tmpOutputsCbor, err := cbor.Encode(tmpOutputs)
286-
if err != nil {
287-
return 0, lcommon.ExUnits{}, nil, err
288-
}
289-
fmt.Printf("tmpOutputs = %x\n", tmpOutputsCbor)
290-
fmt.Printf("sc = %#v\n", sc)
291-
scCbor, err := data.Encode(sc.ToPlutusData())
292-
if err != nil {
293-
return 0, lcommon.ExUnits{}, nil, err
294-
}
295-
fmt.Printf("scCbor = %x\n", scCbor)
296-
}
297-
*/
298386
default:
299387
return 0, lcommon.ExUnits{}, nil, fmt.Errorf("unimplemented script type: %T", tmpScript)
300388
}

0 commit comments

Comments
 (0)