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

feat: add evm cmds #5597

Merged
merged 1 commit into from
Dec 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
325 changes: 325 additions & 0 deletions cmd/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ package cmd
import (
"bytes"
"context"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
Expand All @@ -12,13 +15,19 @@ import (
"time"

"github.com/filecoin-project/go-address"
amt4 "github.com/filecoin-project/go-amt-ipld/v4"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
builtintypes "github.com/filecoin-project/go-state-types/builtin"
"github.com/filecoin-project/go-state-types/builtin/v10/eam"
"github.com/ipfs/go-cid"
cmds "github.com/ipfs/go-ipfs-cmds"
"github.com/multiformats/go-varint"
cbg "github.com/whyrusleeping/cbor-gen"

"github.com/filecoin-project/venus/app/node"
"github.com/filecoin-project/venus/pkg/constants"
"github.com/filecoin-project/venus/venus-shared/actors"
v1api "github.com/filecoin-project/venus/venus-shared/api/chain/v1"
"github.com/filecoin-project/venus/venus-shared/types"
)
Expand All @@ -37,6 +46,8 @@ var chainCmd = &cmds.Command{
"get-receipts": chainGetReceiptsCmd,
"disputer": chainDisputeSetCmd,
"export": chainExportCmd,
"create-evm-actor": ChainExecEVMCmd,
"invoke-evm-actor": ChainInvokeEVMCmd,
},
}

Expand Down Expand Up @@ -441,3 +452,317 @@ func ParseTipSetString(ts string) ([]cid.Cid, error) {

return cids, nil
}

var ChainExecEVMCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Create an new EVM actor via the init actor and return its address",
},
Arguments: []cmds.Argument{
cmds.StringArg("contract", true, false, "contract init code"),
},
Options: []cmds.Option{
cmds.StringOption("from", "optionally specify the account to use for sending the exec message"),
cmds.BoolOption("hes", "use when input contract is in hex"),
},
Run: func(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) error {
if len(req.Arguments) != 1 {
return errors.New("must pass contract init code")
}

ctx := req.Context
contract, err := os.ReadFile(req.Arguments[0])
if err != nil {
return fmt.Errorf("failed to read contract: %w", err)
}
if isHex, _ := req.Options["hex"].(bool); isHex {
contract, err = hex.DecodeString(string(contract))
if err != nil {
return fmt.Errorf("failed to decode contract: %w", err)
}
}

var fromAddr address.Address
from, _ := req.Options["from"].(string)
if len(from) == 0 {
fromAddr, err = env.(*node.Env).WalletAPI.WalletDefaultAddress(ctx)
} else {
fromAddr, err = address.NewFromString(from)
}
if err != nil {
return err
}

nonce, err := env.(*node.Env).MessagePoolAPI.MpoolGetNonce(ctx, fromAddr)
if err != nil {
nonce = 0 // assume a zero nonce on error (e.g. sender doesn't exist).
}

var (
params []byte
method abi.MethodNum
)
if isNativeEthereumAddress(fromAddr) {
params, err = actors.SerializeParams(&eam.CreateParams{
Initcode: contract,
Nonce: nonce,
})
if err != nil {
return fmt.Errorf("failed to serialize Create params: %w", err)
}
method = builtintypes.MethodsEAM.Create
} else {
// TODO this should be able to use Create now; needs new bundle
var salt [32]byte
binary.BigEndian.PutUint64(salt[:], nonce)

params, err = actors.SerializeParams(&eam.Create2Params{
Initcode: contract,
Salt: salt,
})
if err != nil {
return fmt.Errorf("failed to serialize Create2 params: %w", err)
}
method = builtintypes.MethodsEAM.Create2
}

msg := &types.Message{
To: builtintypes.EthereumAddressManagerActorAddr,
From: fromAddr,
Value: big.Zero(),
Method: method,
Params: params,
}

buf := bytes.Buffer{}
afmt := NewSilentWriter(&buf)

// TODO: this is very racy. It may assign a _different_ nonce than the expected one.
afmt.Println("sending message...")
smsg, err := env.(*node.Env).MessagePoolAPI.MpoolPushMessage(ctx, msg, nil)
if err != nil {
return fmt.Errorf("failed to push message: %w", err)
}

afmt.Println("waiting for message to execute...")
wait, err := env.(*node.Env).ChainAPI.StateWaitMsg(ctx, smsg.Cid(), 0, constants.LookbackNoLimit, true)
if err != nil {
return fmt.Errorf("error waiting for message: %w", err)
}

// check it executed successfully
if wait.Receipt.ExitCode != 0 {
return fmt.Errorf("actor execution failed, exitcode %d", wait.Receipt.ExitCode)
}

var result eam.CreateReturn
r := bytes.NewReader(wait.Receipt.Return)
if err := result.UnmarshalCBOR(r); err != nil {
return fmt.Errorf("error unmarshaling return value: %w", err)
}

addr, err := address.NewIDAddress(result.ActorID)
if err != nil {
return err
}
afmt.Printf("Actor ID: %d\n", result.ActorID)
afmt.Printf("ID Address: %s\n", addr)
afmt.Printf("Robust Address: %s\n", result.RobustAddress)
afmt.Printf("Eth Address: %s\n", "0x"+hex.EncodeToString(result.EthAddress[:]))

delegated, err := address.NewDelegatedAddress(builtintypes.EthereumAddressManagerActorID, result.EthAddress[:])
if err != nil {
return fmt.Errorf("failed to calculate f4 address: %w", err)
}

afmt.Printf("f4 Address: %s\n", delegated)

if len(wait.Receipt.Return) > 0 {
result := base64.StdEncoding.EncodeToString(wait.Receipt.Return)
afmt.Printf("Return: %s\n", result)
}

return re.Emit(buf)
},
}

// TODO: Find a home for this.
func isNativeEthereumAddress(addr address.Address) bool {
if addr.Protocol() != address.ID {
return false
}
id, _, err := varint.FromUvarint(addr.Payload())
return err == nil && id == builtintypes.EthereumAddressManagerActorID
}

var ChainInvokeEVMCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Invoke a contract entry point in an EVM actor",
},
Arguments: []cmds.Argument{
cmds.StringArg("address", true, false, ""),
cmds.StringArg("contract-entry-point", true, false, ""),
cmds.StringArg("input-data", false, false, ""),
},
Options: []cmds.Option{
cmds.StringOption("from", "optionally specify the account to use for sending the exec message"),
cmds.Int64Option("value", "optionally specify the value to be sent with the invokation message"),
},
Run: func(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) error {
ctx := req.Context

if argc := len(req.Arguments); argc < 2 || argc > 3 {
return fmt.Errorf("must pass the address, entry point and (optionally) input data")
}

addr, err := address.NewFromString(req.Arguments[0])
if err != nil {
return fmt.Errorf("failed to decode address: %w", err)
}

entryPoint, err := hex.DecodeString(req.Arguments[1])
if err != nil {
return fmt.Errorf("failed to decode hex entry point: %w", err)
}

var inputData []byte
if len(req.Arguments) == 3 {
inputData, err = hex.DecodeString(req.Arguments[2])
if err != nil {
return fmt.Errorf("decoding hex input data: %w", err)
}
}

// TODO need to encode as CBOR bytes now
params := append(entryPoint, inputData...)

var buffer bytes.Buffer
if err := cbg.WriteByteArray(&buffer, params); err != nil {
return fmt.Errorf("failed to encode evm params as cbor: %w", err)
}
params = buffer.Bytes()

var fromAddr address.Address
if from, _ := req.Options["from"].(string); from == "" {
defaddr, err := env.(node.Env).WalletAPI.WalletDefaultAddress(ctx)
if err != nil {
return err
}

fromAddr = defaddr
} else {
addr, err := address.NewFromString(from)
if err != nil {
return err
}

fromAddr = addr
}

value, _ := req.Options["value"].(int64)
val := abi.NewTokenAmount(value)
msg := &types.Message{
To: addr,
From: fromAddr,
Value: val,
Method: abi.MethodNum(2),
Params: params,
}

buf := bytes.Buffer{}
afmt := NewSilentWriter(&buf)
afmt.Println("sending message...")
smsg, err := env.(node.Env).MessagePoolAPI.MpoolPushMessage(ctx, msg, nil)
if err != nil {
return fmt.Errorf("failed to push message: %w", err)
}

afmt.Println("waiting for message to execute...")
wait, err := env.(node.Env).ChainAPI.StateWaitMsg(ctx, smsg.Cid(), 0, constants.LookbackNoLimit, true)
if err != nil {
return fmt.Errorf("error waiting for message: %w", err)
}

// check it executed successfully
if wait.Receipt.ExitCode != 0 {
return fmt.Errorf("actor execution failed")
}

afmt.Println("Gas used: ", wait.Receipt.GasUsed)
result, err := cbg.ReadByteArray(bytes.NewBuffer(wait.Receipt.Return), uint64(len(wait.Receipt.Return)))
if err != nil {
return fmt.Errorf("evm result not correctly encoded: %w", err)
}

if len(result) > 0 {
afmt.Println(hex.EncodeToString(result))
} else {
afmt.Println("OK")
}

if eventsRoot := wait.Receipt.EventsRoot; eventsRoot != nil {
afmt.Println("Events emitted:")

s := &apiIpldStore{ctx, env.(*node.Env).BlockStoreAPI}
amt, err := amt4.LoadAMT(ctx, s, *eventsRoot, amt4.UseTreeBitWidth(5))
if err != nil {
return err
}

var evt types.Event
err = amt.ForEach(ctx, func(u uint64, deferred *cbg.Deferred) error {
fmt.Printf("%x\n", deferred.Raw)
if err := evt.UnmarshalCBOR(bytes.NewReader(deferred.Raw)); err != nil {
return err
}
if err != nil {
return err
}
fmt.Printf("\tEmitter ID: %s\n", evt.Emitter)
for _, e := range evt.Entries {
value, err := cbg.ReadByteArray(bytes.NewBuffer(e.Value), uint64(len(e.Value)))
if err != nil {
return err
}
fmt.Printf("\t\tKey: %s, Value: 0x%x, Flags: b%b\n", e.Key, value, e.Flags)
}
return nil

})
}
if err != nil {
return err
}

return re.Emit(buf)
},
}

type apiIpldStore struct {
ctx context.Context
bsAPI v1api.IBlockStore
}

func (ht *apiIpldStore) Context() context.Context {
return ht.ctx
}

func (ht *apiIpldStore) Get(ctx context.Context, c cid.Cid, out interface{}) error {
raw, err := ht.bsAPI.ChainReadObj(ctx, c)
if err != nil {
return err
}

cu, ok := out.(cbg.CBORUnmarshaler)
if ok {
if err := cu.UnmarshalCBOR(bytes.NewReader(raw)); err != nil {
return err
}
return nil
}

return fmt.Errorf("object does not implement CBORUnmarshaler")
}

func (ht *apiIpldStore) Put(ctx context.Context, v interface{}) (cid.Cid, error) {
panic("No mutations allowed")
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ require (
github.com/fatih/color v1.13.0
github.com/filecoin-project/filecoin-ffi v0.30.4-0.20200910194244-f640612a1a1f
github.com/filecoin-project/go-address v1.1.0
github.com/filecoin-project/go-amt-ipld/v4 v4.0.0
github.com/filecoin-project/go-bitfield v0.2.4
github.com/filecoin-project/go-cbor-util v0.0.1
github.com/filecoin-project/go-commp-utils v0.1.3
Expand Down Expand Up @@ -142,7 +143,6 @@ require (
github.com/elastic/gosigar v0.14.2 // indirect
github.com/filecoin-project/go-amt-ipld/v2 v2.1.1-0.20201006184820-924ee87a1349 // indirect
github.com/filecoin-project/go-amt-ipld/v3 v3.1.0 // indirect
github.com/filecoin-project/go-amt-ipld/v4 v4.0.0 // indirect
github.com/filecoin-project/go-commp-utils/nonffi v0.0.0-20220905160352-62059082a837 // indirect
github.com/filecoin-project/go-ds-versioning v0.1.2 // indirect
github.com/filecoin-project/go-hamt-ipld v0.1.5 // indirect
Expand Down
2 changes: 1 addition & 1 deletion pkg/consensus/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ func (p *DefaultProcessor) ApplyBlocks(ctx context.Context,
}

if ret.Receipt.ExitCode != 0 {
return cid.Undef, nil, fmt.Errorf("reward application message failed exit: %d, reason: %v", ret.Receipt, ret.ActorErr)
return cid.Undef, nil, fmt.Errorf("reward application message failed exit: %d, reason: %v", ret.Receipt.ExitCode, ret.ActorErr)
}

processLog.Debugf("process block %v time %v", index, time.Since(toProcessBlock).Milliseconds())
Expand Down
2 changes: 2 additions & 0 deletions venus-devtool/cborgen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ func main() {
types.BlockMsg{},
types.ExpTipSet{},
types.PaymentInfo{},
types.Event{},
types.EventEntry{},
},
},
{
Expand Down
Loading