diff --git a/cmd/chain.go b/cmd/chain.go index f624875837..781786a937 100644 --- a/cmd/chain.go +++ b/cmd/chain.go @@ -4,6 +4,9 @@ package cmd import ( "bytes" "context" + "encoding/base64" + "encoding/binary" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -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" ) @@ -37,6 +46,8 @@ var chainCmd = &cmds.Command{ "get-receipts": chainGetReceiptsCmd, "disputer": chainDisputeSetCmd, "export": chainExportCmd, + "create-evm-actor": ChainExecEVMCmd, + "invoke-evm-actor": ChainInvokeEVMCmd, }, } @@ -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") +} diff --git a/go.mod b/go.mod index 647e837126..6837cd7cef 100644 --- a/go.mod +++ b/go.mod @@ -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 @@ -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 diff --git a/pkg/consensus/processor.go b/pkg/consensus/processor.go index d0a231338b..5b6d627626 100644 --- a/pkg/consensus/processor.go +++ b/pkg/consensus/processor.go @@ -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()) diff --git a/venus-devtool/cborgen/main.go b/venus-devtool/cborgen/main.go index b7329e6586..bdcb356144 100644 --- a/venus-devtool/cborgen/main.go +++ b/venus-devtool/cborgen/main.go @@ -61,6 +61,8 @@ func main() { types.BlockMsg{}, types.ExpTipSet{}, types.PaymentInfo{}, + types.Event{}, + types.EventEntry{}, }, }, { diff --git a/venus-shared/types/cbor_gen.go b/venus-shared/types/cbor_gen.go index 78c56e72b3..7fdf2501c7 100644 --- a/venus-shared/types/cbor_gen.go +++ b/venus-shared/types/cbor_gen.go @@ -894,7 +894,7 @@ func (t *MessageRoot) UnmarshalCBOR(r io.Reader) (err error) { return nil } -var lengthBufMessageReceipt = []byte{131} +var lengthBufMessageReceipt = []byte{132} func (t *MessageReceipt) MarshalCBOR(w io.Writer) error { if t == nil { @@ -942,6 +942,19 @@ func (t *MessageReceipt) MarshalCBOR(w io.Writer) error { return err } } + + // t.EventsRoot (cid.Cid) (struct) + + if t.EventsRoot == nil { + if _, err := cw.Write(cbg.CborNull); err != nil { + return err + } + } else { + if err := cbg.WriteCid(cw, *t.EventsRoot); err != nil { + return xerrors.Errorf("failed to write cid field t.EventsRoot: %w", err) + } + } + return nil } @@ -964,7 +977,7 @@ func (t *MessageReceipt) UnmarshalCBOR(r io.Reader) (err error) { return fmt.Errorf("cbor input should be of type array") } - if extra != 3 { + if extra != 4 { return fmt.Errorf("cbor input had wrong number of fields") } @@ -1039,6 +1052,28 @@ func (t *MessageReceipt) UnmarshalCBOR(r io.Reader) (err error) { t.GasUsed = int64(extraI) } + // t.EventsRoot (cid.Cid) (struct) + + { + + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + + c, err := cbg.ReadCid(cr) + if err != nil { + return xerrors.Errorf("failed to read cid field t.EventsRoot: %w", err) + } + + t.EventsRoot = &c + } + + } return nil } @@ -1471,3 +1506,224 @@ func (t *PaymentInfo) UnmarshalCBOR(r io.Reader) (err error) { return nil } + +var lengthBufEvent = []byte{130} + +func (t *Event) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufEvent); err != nil { + return err + } + + // t.Emitter (abi.ActorID) (uint64) + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Emitter)); err != nil { + return err + } + + // t.Entries ([]types.EventEntry) (slice) + if len(t.Entries) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.Entries was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Entries))); err != nil { + return err + } + for _, v := range t.Entries { + if err := v.MarshalCBOR(cw); err != nil { + return err + } + } + return nil +} + +func (t *Event) UnmarshalCBOR(r io.Reader) (err error) { + *t = Event{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 2 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Emitter (abi.ActorID) (uint64) + + { + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Emitter = abi.ActorID(extra) + + } + // t.Entries ([]types.EventEntry) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.Entries: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Entries = make([]EventEntry, extra) + } + + for i := 0; i < int(extra); i++ { + + var v EventEntry + if err := v.UnmarshalCBOR(cr); err != nil { + return err + } + + t.Entries[i] = v + } + + return nil +} + +var lengthBufEventEntry = []byte{131} + +func (t *EventEntry) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufEventEntry); err != nil { + return err + } + + // t.Flags (uint8) (uint8) + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Flags)); err != nil { + return err + } + + // t.Key (string) (string) + if len(t.Key) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.Key was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Key))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.Key)); err != nil { + return err + } + + // t.Value ([]uint8) (slice) + if len(t.Value) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field t.Value was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.Value))); err != nil { + return err + } + + if _, err := cw.Write(t.Value[:]); err != nil { + return err + } + return nil +} + +func (t *EventEntry) UnmarshalCBOR(r io.Reader) (err error) { + *t = EventEntry{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 3 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Flags (uint8) (uint8) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint8 field") + } + if extra > math.MaxUint8 { + return fmt.Errorf("integer in input was too large for uint8 field") + } + t.Flags = uint8(extra) + // t.Key (string) (string) + + { + sval, err := cbg.ReadString(cr) + if err != nil { + return err + } + + t.Key = string(sval) + } + // t.Value ([]uint8) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.Value: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.Value = make([]uint8, extra) + } + + if _, err := io.ReadFull(cr, t.Value[:]); err != nil { + return err + } + return nil +} diff --git a/venus-shared/types/event.go b/venus-shared/types/event.go new file mode 100644 index 0000000000..00c25ca4cb --- /dev/null +++ b/venus-shared/types/event.go @@ -0,0 +1,32 @@ +package types + +import ( + "github.com/filecoin-project/go-state-types/abi" +) + +type Event struct { + // The ID of the actor that emitted this event. + Emitter abi.ActorID + + // Key values making up this event. + Entries []EventEntry +} + +type EventEntry struct { + // A bitmap conveying metadata or hints about this entry. + Flags uint8 + + // The key of this event entry + Key string + + // Any DAG-CBOR encodeable type. + Value []byte +} + +type FilterID [32]byte // compatible with EthHash + +// EventEntry flags defined in fvm_shared +const ( + EventFlagIndexedKey = 0b00000001 + EventFlagIndexedValue = 0b00000010 +) diff --git a/venus-shared/types/message_receipt.go b/venus-shared/types/message_receipt.go index 8140272037..2bb8f7fbbd 100644 --- a/venus-shared/types/message_receipt.go +++ b/venus-shared/types/message_receipt.go @@ -1,17 +1,64 @@ package types import ( + "bytes" "encoding/json" "fmt" "github.com/filecoin-project/go-state-types/exitcode" + "github.com/ipfs/go-cid" +) + +type MessageReceiptVersion byte + +const ( + // MessageReceiptV0 refers to pre FIP-0049 receipts. + MessageReceiptV0 MessageReceiptVersion = 0 + // MessageReceiptV1 refers to post FIP-0049 receipts. + MessageReceiptV1 MessageReceiptVersion = 1 ) // MessageReceipt is what is returned by executing a message on the vm. type MessageReceipt struct { + version MessageReceiptVersion + ExitCode exitcode.ExitCode Return []byte GasUsed int64 + + EventsRoot *cid.Cid // Root of Event AMT +} + +// NewMessageReceiptV0 creates a new pre FIP-0049 receipt with no capability to +// convey events. +func NewMessageReceiptV0(exitcode exitcode.ExitCode, ret []byte, gasUsed int64) MessageReceipt { + return MessageReceipt{ + version: MessageReceiptV0, + ExitCode: exitcode, + Return: ret, + GasUsed: gasUsed, + } +} + +// NewMessageReceiptV1 creates a new pre FIP-0049 receipt with the ability to +// convey events. +func NewMessageReceiptV1(exitcode exitcode.ExitCode, ret []byte, gasUsed int64, eventsRoot *cid.Cid) MessageReceipt { + return MessageReceipt{ + version: MessageReceiptV1, + ExitCode: exitcode, + Return: ret, + GasUsed: gasUsed, + EventsRoot: eventsRoot, + } +} + +func (r *MessageReceipt) Version() MessageReceiptVersion { + return r.version +} + +func (r *MessageReceipt) Equals(o *MessageReceipt) bool { + return r.version == o.version && r.ExitCode == o.ExitCode && bytes.Equal(r.Return, o.Return) && r.GasUsed == o.GasUsed && + (r.EventsRoot == o.EventsRoot || (r.EventsRoot != nil && o.EventsRoot != nil && *r.EventsRoot == *o.EventsRoot)) } func (r *MessageReceipt) String() string {