@@ -27,6 +27,10 @@ import (
2727 "github.com/blinklabs-io/cardano-node-api/internal/node"
2828 "github.com/blinklabs-io/gouroboros/ledger"
2929 ocommon "github.com/blinklabs-io/gouroboros/protocol/common"
30+ "github.com/blinklabs-io/plutigo/cek"
31+ "github.com/blinklabs-io/plutigo/data"
32+ "github.com/blinklabs-io/plutigo/syn"
33+ "github.com/utxorpc/go-codegen/utxorpc/v1alpha/cardano"
3034 submit "github.com/utxorpc/go-codegen/utxorpc/v1alpha/submit"
3135 "github.com/utxorpc/go-codegen/utxorpc/v1alpha/submit/submitconnect"
3236)
@@ -83,6 +87,238 @@ func (s *submitServiceServer) SubmitTx(
8387 return connect .NewResponse (resp ), nil
8488}
8589
90+ // evaluateScript evaluates a Plutus script with the given arguments and cost model
91+ func evaluateScript (scriptBytes []byte , args []data.PlutusData , costModel map [uint ][]int64 ) (uint64 , uint64 , error ) {
92+ // Decode the script
93+ program , err := syn.Decode [syn.DeBruijn ](scriptBytes )
94+ if err != nil {
95+ return 0 , 0 , fmt .Errorf ("decode script: %w" , err )
96+ }
97+
98+ // Apply arguments to the script
99+ term := program .Term
100+ for _ , arg := range args {
101+ term = & syn.Apply [syn.DeBruijn ]{
102+ Function : term ,
103+ Argument : & syn.Constant {
104+ Con : & syn.Data {Inner : arg },
105+ },
106+ }
107+ }
108+
109+ // Create machine with version-based cost model
110+ machine := cek .NewMachineWithVersionCosts [syn.DeBruijn ](program .Version , 200 )
111+
112+ _ , err = machine .Run (term )
113+ if err != nil {
114+ return 0 , 0 , fmt .Errorf ("execute script: %w" , err )
115+ }
116+
117+ return uint64 (machine .ExBudget .Cpu ), uint64 (machine .ExBudget .Mem ), nil
118+ }
119+
120+ // convertPlutusData converts plutigo data.PlutusData to *cardano.PlutusData
121+ func convertPlutusData (pd data.PlutusData ) * cardano.PlutusData {
122+ switch v := pd .(type ) {
123+ case * data.Constr :
124+ fields := make ([]* cardano.PlutusData , len (v .Fields ))
125+ for i , field := range v .Fields {
126+ fields [i ] = convertPlutusData (field )
127+ }
128+ return & cardano.PlutusData {
129+ PlutusData : & cardano.PlutusData_Constr {
130+ Constr : & cardano.Constr {
131+ Tag : uint32 (v .Tag ),
132+ Fields : fields ,
133+ },
134+ },
135+ }
136+ case * data.Map :
137+ pairs := make ([]* cardano.PlutusDataPair , len (v .Pairs ))
138+ for i , pair := range v .Pairs {
139+ pairs [i ] = & cardano.PlutusDataPair {
140+ Key : convertPlutusData (pair [0 ]),
141+ Value : convertPlutusData (pair [1 ]),
142+ }
143+ }
144+ return & cardano.PlutusData {
145+ PlutusData : & cardano.PlutusData_Map {
146+ Map : & cardano.PlutusDataMap {
147+ Pairs : pairs ,
148+ },
149+ },
150+ }
151+ case * data.Integer :
152+ return & cardano.PlutusData {
153+ PlutusData : & cardano.PlutusData_BigInt {
154+ BigInt : & cardano.BigInt {
155+ BigInt : & cardano.BigInt_Int {
156+ Int : v .Inner .Int64 (),
157+ },
158+ },
159+ },
160+ }
161+ case * data.ByteString :
162+ return & cardano.PlutusData {
163+ PlutusData : & cardano.PlutusData_BoundedBytes {
164+ BoundedBytes : v .Inner ,
165+ },
166+ }
167+ case * data.List :
168+ items := make ([]* cardano.PlutusData , len (v .Items ))
169+ for i , item := range v .Items {
170+ items [i ] = convertPlutusData (item )
171+ }
172+ return & cardano.PlutusData {
173+ PlutusData : & cardano.PlutusData_Array {
174+ Array : & cardano.PlutusDataArray {
175+ Items : items ,
176+ },
177+ },
178+ }
179+ default :
180+ // Should not happen
181+ return nil
182+ }
183+ }
184+
185+ // EvalTx
186+ func (s * submitServiceServer ) EvalTx (
187+ ctx context.Context ,
188+ req * connect.Request [submit.EvalTxRequest ],
189+ ) (* connect.Response [submit.EvalTxResponse ], error ) {
190+ // txRaw
191+ txRaw := req .Msg .GetTx () // *AnyChainTx
192+ if txRaw == nil {
193+ return nil , errors .New ("transaction is required" )
194+ }
195+ log .Printf ("Got an EvalTx request" )
196+
197+ resp := & submit.EvalTxResponse {}
198+
199+ // Connect to node
200+ oConn , err := node .GetConnection (nil )
201+ if err != nil {
202+ return connect .NewResponse (resp ), err
203+ }
204+ defer func () {
205+ // Close Ouroboros connection
206+ oConn .Close ()
207+ }()
208+
209+ // Parse the transaction
210+ txRawBytes := txRaw .GetRaw () // raw bytes
211+ txType , err := ledger .DetermineTransactionType (txRawBytes )
212+ if err != nil {
213+ return connect .NewResponse (resp ), err
214+ }
215+ tx , err := ledger .NewTransactionFromCbor (txType , txRawBytes )
216+ if err != nil {
217+ return connect .NewResponse (resp ), err
218+ }
219+
220+ // Get protocol parameters for cost models
221+ oConn .LocalStateQuery ().Client .Start ()
222+ protoParams , err := oConn .LocalStateQuery ().Client .GetCurrentProtocolParams ()
223+ if err != nil {
224+ return connect .NewResponse (resp ), err
225+ }
226+
227+ // Get witnesses
228+ witnesses := tx .Witnesses ()
229+ redeemers := witnesses .Redeemers ()
230+
231+ // Get cost models from protocol parameters
232+ var costModels map [uint ][]int64
233+ if conwayParams , ok := protoParams .(* ledger.ConwayProtocolParameters ); ok {
234+ costModels = conwayParams .CostModels
235+ }
236+
237+ // Get scripts
238+ v1Scripts := witnesses .PlutusV1Scripts ()
239+ v2Scripts := witnesses .PlutusV2Scripts ()
240+ v3Scripts := witnesses .PlutusV3Scripts ()
241+ allScripts := make ([][]byte , 0 , len (v1Scripts )+ len (v2Scripts )+ len (v3Scripts ))
242+ scriptVersions := make ([]uint , 0 , len (v1Scripts )+ len (v2Scripts )+ len (v3Scripts ))
243+ for _ , s := range v1Scripts {
244+ allScripts = append (allScripts , s .RawScriptBytes ())
245+ scriptVersions = append (scriptVersions , 1 )
246+ }
247+ for _ , s := range v2Scripts {
248+ allScripts = append (allScripts , s .RawScriptBytes ())
249+ scriptVersions = append (scriptVersions , 2 )
250+ }
251+ for _ , s := range v3Scripts {
252+ allScripts = append (allScripts , s .RawScriptBytes ())
253+ scriptVersions = append (scriptVersions , 3 )
254+ }
255+
256+ var txEvalRedeemers []* cardano.Redeemer
257+ for key , value := range redeemers .Iter () {
258+ redeemer := & cardano.Redeemer {
259+ Purpose : cardano .RedeemerPurpose (key .Tag ),
260+ Payload : convertPlutusData (value .Data .Data ),
261+ Index : key .Index ,
262+ ExUnits : & cardano.ExUnits {
263+ Steps : 0 ,
264+ Memory : 0 ,
265+ },
266+ }
267+
268+ // Try to evaluate if we have a script for this redeemer
269+ if int (key .Index ) < len (allScripts ) {
270+ scriptBytes := allScripts [key .Index ]
271+ version := scriptVersions [key .Index ]
272+
273+ // Build arguments based on purpose
274+ var args []data.PlutusData
275+
276+ // For now, only handle spending scripts with datum, redeemer, context
277+ if key .Tag == 0 { // RedeemerTagSpend
278+ // Get datum - for simplicity, assume it's the redeemer data for now
279+ datum := value .Data .Data
280+ redeemerData := value .Data .Data
281+ // TODO: build proper script context
282+ contextData := data .NewConstr (0 ) // dummy context
283+
284+ args = []data.PlutusData {datum , redeemerData , contextData }
285+ } else {
286+ // For other purposes, just redeemer and context
287+ redeemerData := value .Data .Data
288+ contextData := data .NewConstr (0 ) // dummy context
289+ args = []data.PlutusData {redeemerData , contextData }
290+ }
291+
292+ // Filter cost model for this version
293+ versionCostModel := make (map [uint ][]int64 )
294+ if model , ok := costModels [version ]; ok {
295+ versionCostModel [version ] = model
296+ }
297+
298+ steps , memory , err := evaluateScript (scriptBytes , args , versionCostModel )
299+ if err != nil {
300+ log .Printf ("Failed to evaluate script for redeemer %d: %v" , key .Index , err )
301+ } else {
302+ redeemer .ExUnits .Steps = steps
303+ redeemer .ExUnits .Memory = memory
304+ }
305+ }
306+
307+ txEvalRedeemers = append (txEvalRedeemers , redeemer )
308+ }
309+
310+ resp .Report = & submit.AnyChainEval {
311+ Chain : & submit.AnyChainEval_Cardano {
312+ Cardano : & cardano.TxEval {
313+ Fee : nil , // TODO: set fee
314+ Redeemers : txEvalRedeemers ,
315+ },
316+ },
317+ }
318+
319+ return connect .NewResponse (resp ), nil
320+ }
321+
86322func (s * submitServiceServer ) WaitForTx (
87323 ctx context.Context ,
88324 req * connect.Request [submit.WaitForTxRequest ],
0 commit comments