Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add simulateTransaction endpoint #1610

Merged
merged 29 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
45749b1
initial version
tsachiherman Sep 30, 2024
23c8b1d
Merge branch 'main' into tsachi/simulate_transaction
tsachiherman Sep 30, 2024
65df47b
update
tsachiherman Sep 30, 2024
6fc64c9
linter
tsachiherman Sep 30, 2024
693b7c0
update
tsachiherman Oct 1, 2024
0da1d84
rollback unwanted changes
tsachiherman Oct 1, 2024
1a2e340
correct typo
tsachiherman Oct 1, 2024
8253c81
Merge branch 'main' into tsachi/simulate_transaction
tsachiherman Oct 1, 2024
7d26831
add encoding to state keys.
tsachiherman Oct 1, 2024
75c07e6
update
tsachiherman Oct 2, 2024
a306b46
Merge branch 'main' into tsachi/simulate_transaction
tsachiherman Oct 2, 2024
5379398
remove comments
tsachiherman Oct 2, 2024
ff86ecf
Merge branch 'tsachi/simulate_transaction' of github.com:ava-labs/hyp…
tsachiherman Oct 2, 2024
f42b284
Merge branch 'main' into tsachi/simulate_transaction
tsachiherman Oct 3, 2024
c08b8cf
add keys marshaler
tsachiherman Oct 3, 2024
793a6b0
step
tsachiherman Oct 3, 2024
7f3868e
Merge branch 'main' into tsachi/simulate_transaction
tsachiherman Oct 4, 2024
5399ff3
update recorder.
tsachiherman Oct 7, 2024
c172c59
update
tsachiherman Oct 7, 2024
89a134d
update
tsachiherman Oct 8, 2024
f5d4dd8
update
tsachiherman Oct 8, 2024
e3855fe
linter
tsachiherman Oct 8, 2024
9c74334
update per CR
tsachiherman Oct 9, 2024
267e13b
lint
tsachiherman Oct 9, 2024
b6bd4f2
Update state/recorder.go
tsachiherman Oct 9, 2024
e51d8fd
Update state/recorder.go
tsachiherman Oct 9, 2024
689d0ec
Update state/recorder.go
tsachiherman Oct 9, 2024
8f020d8
fix PR
tsachiherman Oct 9, 2024
65c9c42
Merge branch 'main' into tsachi/simulate_transaction
tsachiherman Oct 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions api/jsonrpc/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,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
}
70 changes: 70 additions & 0 deletions 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 Down Expand Up @@ -230,3 +231,72 @@ 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()
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 errors.New("tx has extra bytes")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can we fix the error message here and move the error definition to the top of the file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}
actions = append(actions, action)
}
if len(actions) == 0 {
return errors.New("simulateAction expects at least a single action, none found")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we move this error definition to a variable at the top of the file? Perhaps errSimulateZeroActions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}
currentState, err := j.vm.ImmutableState(ctx)
if err != nil {
return err
}

recorder := state.NewRecorder(currentState)

currentTime := time.Now().UnixMilli()
for _, action := range actions {
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
}
tsachiherman marked this conversation as resolved.
Show resolved Hide resolved
actionResult.StateKeys = recorder.GetStateKeys()
reply.ActionResults = append(reply.ActionResults, actionResult)
// create another recorder to recored the next action.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// create another recorder to recored the next action.
// create another recorder to record the next action.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

recorder = state.NewRecorder(recorder)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit - is there a reason that we need to chain the recorders this way?

It seems like this should be fine because each recorder just takes the place of state.Immutable.

Could we move creating the first recorder into the loop, so that the top of the loop has recorder := state.NewRecorder(currentState) and we update currentState = recorder at the bottom of the loop?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, done.

}
return nil
}
22 changes: 17 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,9 +139,16 @@ 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 {
return mconsts.ResultOutputID
}

// Size is the number of bytes it takes to represent this [Action]. This is used to preallocate
// memory during encoding and to charge bandwidth fees.
func (r *Result) Size() int {
return consts.Uint64Len + consts.Uint32Len + len(r.Value)
}
27 changes: 23 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,6 +17,7 @@ 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"
)

Expand Down Expand Up @@ -141,18 +144,34 @@ var callCmd = &cobra.Command{
ContractAddress: contractAddress,
Value: amount,
Function: function,
Fuel: uint64(1000000000),
}

specifiedStateKeysSet, fuel, err := bcli.Simulate(ctx, *action, priv.Address)
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]

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 errors.New("returned output from SimulateActions was not actions.Result")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we move this error definition to the top of the file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}

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
Loading