Skip to content

Commit

Permalink
add simulateTransaction endpoint (#1610)
Browse files Browse the repository at this point in the history
Signed-off-by: Tsachi Herman <[email protected]>
  • Loading branch information
tsachiherman authored Oct 10, 2024
1 parent cbb6a33 commit b3c991f
Show file tree
Hide file tree
Showing 11 changed files with 580 additions and 159 deletions.
27 changes: 27 additions & 0 deletions api/jsonrpc/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,3 +239,30 @@ func Wait(ctx context.Context, interval time.Duration, check func(ctx context.Co
}
return ctx.Err()
}

func (cli *JSONRPCClient) SimulateActions(ctx context.Context, actions chain.Actions, actor codec.Address) ([]SimulateActionResult, error) {
args := &SimulatActionsArgs{
Actor: actor,
}

for _, action := range actions {
marshaledAction, err := chain.MarshalTyped(action)
if err != nil {
return nil, err
}
args.Actions = append(args.Actions, marshaledAction)
}

resp := new(SimulateActionsReply)
err := cli.requester.SendRequest(
ctx,
"simulateActions",
args,
resp,
)
if err != nil {
return nil, err
}

return resp.ActionResults, nil
}
75 changes: 74 additions & 1 deletion api/jsonrpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/ava-labs/hypersdk/codec"
"github.com/ava-labs/hypersdk/consts"
"github.com/ava-labs/hypersdk/fees"
"github.com/ava-labs/hypersdk/state"
"github.com/ava-labs/hypersdk/state/tstate"
)

Expand All @@ -27,6 +28,11 @@ const (

var _ api.HandlerFactory[api.VM] = (*JSONRPCServerFactory)(nil)

var (
errSimulateZeroActions = errors.New("simulateAction expects at least a single action, none found")
errTransactionExtraBytes = errors.New("transaction has extra bytes")
)

type JSONRPCServerFactory struct{}

func (JSONRPCServerFactory) New(vm api.VM) (api.Handler, error) {
Expand Down Expand Up @@ -95,7 +101,7 @@ func (j *JSONRPCServer) SubmitTx(
return fmt.Errorf("%w: unable to unmarshal on public service", err)
}
if !rtx.Empty() {
return errors.New("tx has extra bytes")
return errTransactionExtraBytes
}
if err := tx.Verify(ctx); err != nil {
return err
Expand Down Expand Up @@ -237,3 +243,70 @@ func (j *JSONRPCServer) Execute(
}
return nil
}

type SimulatActionsArgs struct {
Actions []codec.Bytes `json:"actions"`
Actor codec.Address `json:"actor"`
}

type SimulateActionResult struct {
Output codec.Bytes `json:"output"`
StateKeys state.Keys `json:"stateKeys"`
}

type SimulateActionsReply struct {
ActionResults []SimulateActionResult `json:"actionresults"`
}

func (j *JSONRPCServer) SimulateActions(
req *http.Request,
args *SimulatActionsArgs,
reply *SimulateActionsReply,
) error {
ctx, span := j.vm.Tracer().Start(req.Context(), "JSONRPCServer.SimulateActions")
defer span.End()

actionRegistry := j.vm.ActionRegistry()

Check failure on line 269 in api/jsonrpc/server.go

View workflow job for this annotation

GitHub Actions / hypersdk-unit-tests

j.vm.ActionRegistry undefined (type "github.com/ava-labs/hypersdk/api".VM has no field or method ActionRegistry)

Check failure on line 269 in api/jsonrpc/server.go

View workflow job for this annotation

GitHub Actions / hypersdk-benchmark-tests

j.vm.ActionRegistry undefined (type "github.com/ava-labs/hypersdk/api".VM has no field or method ActionRegistry)

Check failure on line 269 in api/jsonrpc/server.go

View workflow job for this annotation

GitHub Actions / hypersdk-lint

j.vm.ActionRegistry undefined (type "github.com/ava-labs/hypersdk/api".VM has no field or method ActionRegistry) (typecheck)

Check failure on line 269 in api/jsonrpc/server.go

View workflow job for this annotation

GitHub Actions / hypersdk-lint

j.vm.ActionRegistry undefined (type "github.com/ava-labs/hypersdk/api".VM has no field or method ActionRegistry)) (typecheck)

Check failure on line 269 in api/jsonrpc/server.go

View workflow job for this annotation

GitHub Actions / hypersdk-lint

j.vm.ActionRegistry undefined (type "github.com/ava-labs/hypersdk/api".VM has no field or method ActionRegistry)) (typecheck)

Check failure on line 269 in api/jsonrpc/server.go

View workflow job for this annotation

GitHub Actions / hypersdk-lint

j.vm.ActionRegistry undefined (type "github.com/ava-labs/hypersdk/api".VM has no field or method ActionRegistry)) (typecheck)

Check failure on line 269 in api/jsonrpc/server.go

View workflow job for this annotation

GitHub Actions / hypersdk-lint

j.vm.ActionRegistry undefined (type "github.com/ava-labs/hypersdk/api".VM has no field or method ActionRegistry)) (typecheck)
var actions chain.Actions
for _, actionBytes := range args.Actions {
actionsReader := codec.NewReader(actionBytes, len(actionBytes))
action, err := (*actionRegistry).Unmarshal(actionsReader)
if err != nil {
return err
}
if !actionsReader.Empty() {
return errTransactionExtraBytes
}
actions = append(actions, action)
}
if len(actions) == 0 {
return errSimulateZeroActions
}
currentState, err := j.vm.ImmutableState(ctx)
if err != nil {
return err
}

currentTime := time.Now().UnixMilli()
for _, action := range actions {
recorder := state.NewRecorder(currentState)
actionOutput, err := action.Execute(ctx, j.vm.Rules(currentTime), recorder, currentTime, args.Actor, ids.Empty)

var actionResult SimulateActionResult
if actionOutput == nil {
actionResult.Output = []byte{}
} else {
actionResult.Output, err = chain.MarshalTyped(actionOutput)
if err != nil {
return fmt.Errorf("failed to marshal output: %w", err)
}
}
if err != nil {
return err
}
actionResult.StateKeys = recorder.GetStateKeys()
reply.ActionResults = append(reply.ActionResults, actionResult)
currentState = recorder
}
return nil
}
16 changes: 11 additions & 5 deletions examples/vmwithcontracts/actions/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ import (

var _ chain.Action = (*Call)(nil)

const MaxCallDataSize = units.MiB
const (
MaxCallDataSize = units.MiB
MaxResultSizeLimit = units.MiB
)

type StateKeyPermission struct {
Key string
Expand Down Expand Up @@ -68,7 +71,7 @@ func (t *Call) Execute(
actor codec.Address,
_ ids.ID,
) (codec.Typed, error) {
resutBytes, err := t.r.CallContract(ctx, &runtime.CallInfo{
callInfo := &runtime.CallInfo{
Contract: t.ContractAddress,
Actor: actor,
State: &storage.ContractStateManager{Mutable: mu},
Expand All @@ -77,11 +80,13 @@ func (t *Call) Execute(
Timestamp: uint64(timestamp),
Fuel: t.Fuel,
Value: t.Value,
})
}
resultBytes, err := t.r.CallContract(ctx, callInfo)
if err != nil {
return nil, err
}
return &Result{Value: resutBytes}, nil
consumedFuel := t.Fuel - callInfo.RemainingFuel()
return &Result{Value: resultBytes, ConsumedFuel: consumedFuel}, nil
}

func (t *Call) ComputeUnits(chain.Rules) uint64 {
Expand Down Expand Up @@ -134,7 +139,8 @@ func (*Call) ValidRange(chain.Rules) (int64, int64) {
}

type Result struct {
Value []byte `serialize:"true" json:"value"`
Value []byte `serialize:"true" json:"value"`
ConsumedFuel uint64 `serialize:"true" json:"consumedfuel"`
}

func (*Result) GetTypeID() uint8 {
Expand Down
29 changes: 25 additions & 4 deletions examples/vmwithcontracts/cmd/vmwithcontracts-cli/cmd/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package cmd

import (
"context"
"errors"
"fmt"
"os"

"github.com/near/borsh-go"
Expand All @@ -15,9 +17,12 @@ import (
"github.com/ava-labs/hypersdk/cli/prompt"
"github.com/ava-labs/hypersdk/codec"
"github.com/ava-labs/hypersdk/examples/vmwithcontracts/actions"
"github.com/ava-labs/hypersdk/examples/vmwithcontracts/vm"
"github.com/ava-labs/hypersdk/utils"
)

var errUnexpectedSimulateActionsOutput = errors.New("returned output from SimulateActions was not actions.Result")

var actionCmd = &cobra.Command{
Use: "action",
RunE: func(*cobra.Command, []string) error {
Expand Down Expand Up @@ -141,18 +146,34 @@ var callCmd = &cobra.Command{
ContractAddress: contractAddress,
Value: amount,
Function: function,
Fuel: uint64(1000000000),
}

actionSimulationResults, err := cli.SimulateActions(ctx, chain.Actions{action}, priv.Address)
if err != nil {
return err
}
if len(actionSimulationResults) != 1 {
return fmt.Errorf("unexpected number of returned actions. One action expected, %d returned", len(actionSimulationResults))
}
actionSimulationResult := actionSimulationResults[0]

specifiedStateKeysSet, fuel, err := bcli.Simulate(ctx, *action, priv.Address)
rtx := codec.NewReader(actionSimulationResult.Output, len(actionSimulationResult.Output))

simulationResultOutput, err := (*vm.OutputParser).Unmarshal(rtx)
if err != nil {
return err
}
simulationResult, ok := simulationResultOutput.(*actions.Result)
if !ok {
return errUnexpectedSimulateActionsOutput
}

action.SpecifiedStateKeys = make([]actions.StateKeyPermission, 0, len(specifiedStateKeysSet))
for key, value := range specifiedStateKeysSet {
action.SpecifiedStateKeys = make([]actions.StateKeyPermission, 0, len(actionSimulationResult.StateKeys))
for key, value := range actionSimulationResult.StateKeys {
action.SpecifiedStateKeys = append(action.SpecifiedStateKeys, actions.StateKeyPermission{Key: key, Permission: value})
}
action.Fuel = fuel
action.Fuel = simulationResult.ConsumedFuel

// Confirm action
cont, err := prompt.Continue()
Expand Down
70 changes: 0 additions & 70 deletions examples/vmwithcontracts/storage/recorder.go

This file was deleted.

26 changes: 0 additions & 26 deletions examples/vmwithcontracts/vm/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,17 @@ package vm

import (
"context"
"encoding/hex"
"encoding/json"
"strings"
"time"

"github.com/ava-labs/hypersdk/api/jsonrpc"
"github.com/ava-labs/hypersdk/chain"
"github.com/ava-labs/hypersdk/codec"
"github.com/ava-labs/hypersdk/examples/vmwithcontracts/actions"
"github.com/ava-labs/hypersdk/examples/vmwithcontracts/consts"
"github.com/ava-labs/hypersdk/examples/vmwithcontracts/storage"
"github.com/ava-labs/hypersdk/genesis"
"github.com/ava-labs/hypersdk/requester"
"github.com/ava-labs/hypersdk/state"
"github.com/ava-labs/hypersdk/utils"
)

Expand Down Expand Up @@ -137,26 +134,3 @@ func CreateParser(genesisBytes []byte) (chain.Parser, error) {
}
return NewParser(&genesis), nil
}

func (cli *JSONRPCClient) Simulate(ctx context.Context, callTx actions.Call, actor codec.Address) (state.Keys, uint64, error) {
resp := new(SimulateCallTxReply)
err := cli.requester.SendRequest(
ctx,
"simulateCallContractTx",
&SimulateCallTxArgs{CallTx: callTx, Actor: actor},
resp,
)
if err != nil {
return nil, 0, err
}
result := state.Keys{}
for _, entry := range resp.StateKeys {
hexBytes, err := hex.DecodeString(entry.HexKey)
if err != nil {
return nil, 0, err
}

result.Add(string(hexBytes), state.Permissions(entry.Permissions))
}
return result, resp.FuelConsumed, nil
}
Loading

0 comments on commit b3c991f

Please sign in to comment.