diff --git a/chain/actors/builtin/multisig/message.go b/chain/actors/builtin/multisig/message.go new file mode 100644 index 000000000..b19287432 --- /dev/null +++ b/chain/actors/builtin/multisig/message.go @@ -0,0 +1,70 @@ +package multisig + +import ( + "fmt" + + "github.com/minio/blake2b-simd" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + multisig2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/multisig" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/types" +) + +func Message(version actors.Version, from address.Address) MessageBuilder { + switch version { + case actors.Version0: + return message0{from} + case actors.Version2: + return message2{message0{from}} + default: + panic(fmt.Sprintf("unsupported actors version: %d", version)) + } +} + +type MessageBuilder interface { + // Create a new multisig with the specified parameters. + Create(signers []address.Address, threshold uint64, + vestingStart, vestingDuration abi.ChainEpoch, + initialAmount abi.TokenAmount) (*types.Message, error) + + // Propose a transaction to the given multisig. + Propose(msig, target address.Address, amt abi.TokenAmount, + method abi.MethodNum, params []byte) (*types.Message, error) + + // Approve a multisig transaction. The "hash" is optional. + Approve(msig address.Address, txID uint64, hash *ProposalHashData) (*types.Message, error) + + // Cancel a multisig transaction. The "hash" is optional. + Cancel(msig address.Address, txID uint64, hash *ProposalHashData) (*types.Message, error) +} + +// this type is the same between v0 and v2 +type ProposalHashData = multisig2.ProposalHashData + +func txnParams(id uint64, data *ProposalHashData) ([]byte, error) { + params := multisig2.TxnIDParams{ID: multisig2.TxnID(id)} + if data != nil { + if data.Requester.Protocol() != address.ID { + return nil, xerrors.Errorf("proposer address must be an ID address, was %s", data.Requester) + } + if data.Value.Sign() == -1 { + return nil, xerrors.Errorf("proposal value must be non-negative, was %s", data.Value) + } + if data.To == address.Undef { + return nil, xerrors.Errorf("proposed destination address must be set") + } + pser, err := data.Serialize() + if err != nil { + return nil, err + } + hash := blake2b.Sum256(pser) + params.ProposalHash = hash[:] + } + + return actors.SerializeParams(¶ms) +} diff --git a/chain/actors/builtin/multisig/message0.go b/chain/actors/builtin/multisig/message0.go new file mode 100644 index 000000000..dc43a9d5d --- /dev/null +++ b/chain/actors/builtin/multisig/message0.go @@ -0,0 +1,142 @@ +package multisig + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + init0 "github.com/filecoin-project/specs-actors/actors/builtin/init" + multisig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message0 struct{ from address.Address } + +func (m message0) Create( + signers []address.Address, threshold uint64, + unlockStart, unlockDuration abi.ChainEpoch, + initialAmount abi.TokenAmount, +) (*types.Message, error) { + + lenAddrs := uint64(len(signers)) + + if lenAddrs < threshold { + return nil, xerrors.Errorf("cannot require signing of more addresses than provided for multisig") + } + + if threshold == 0 { + threshold = lenAddrs + } + + if m.from == address.Undef { + return nil, xerrors.Errorf("must provide source address") + } + + if unlockStart != 0 { + return nil, xerrors.Errorf("actors v0 does not support a non-zero vesting start time") + } + + // Set up constructor parameters for multisig + msigParams := &multisig0.ConstructorParams{ + Signers: signers, + NumApprovalsThreshold: threshold, + UnlockDuration: unlockDuration, + } + + enc, actErr := actors.SerializeParams(msigParams) + if actErr != nil { + return nil, actErr + } + + // new actors are created by invoking 'exec' on the init actor with the constructor params + execParams := &init0.ExecParams{ + CodeCID: builtin0.MultisigActorCodeID, + ConstructorParams: enc, + } + + enc, actErr = actors.SerializeParams(execParams) + if actErr != nil { + return nil, actErr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Method: builtin0.MethodsInit.Exec, + Params: enc, + Value: initialAmount, + }, nil +} + +func (m message0) Propose(msig, to address.Address, amt abi.TokenAmount, + method abi.MethodNum, params []byte) (*types.Message, error) { + + if msig == address.Undef { + return nil, xerrors.Errorf("must provide a multisig address for proposal") + } + + if to == address.Undef { + return nil, xerrors.Errorf("must provide a target address for proposal") + } + + if amt.Sign() == -1 { + return nil, xerrors.Errorf("must provide a non-negative amount for proposed send") + } + + if m.from == address.Undef { + return nil, xerrors.Errorf("must provide source address") + } + + enc, actErr := actors.SerializeParams(&multisig0.ProposeParams{ + To: to, + Value: amt, + Method: method, + Params: params, + }) + if actErr != nil { + return nil, xerrors.Errorf("failed to serialize parameters: %w", actErr) + } + + return &types.Message{ + To: msig, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin0.MethodsMultisig.Propose, + Params: enc, + }, nil +} + +func (m message0) Approve(msig address.Address, txID uint64, hashData *ProposalHashData) (*types.Message, error) { + enc, err := txnParams(txID, hashData) + if err != nil { + return nil, err + } + + return &types.Message{ + To: msig, + From: m.from, + Value: types.NewInt(0), + Method: builtin0.MethodsMultisig.Approve, + Params: enc, + }, nil +} + +func (m message0) Cancel(msig address.Address, txID uint64, hashData *ProposalHashData) (*types.Message, error) { + enc, err := txnParams(txID, hashData) + if err != nil { + return nil, err + } + + return &types.Message{ + To: msig, + From: m.from, + Value: types.NewInt(0), + Method: builtin0.MethodsMultisig.Cancel, + Params: enc, + }, nil +} diff --git a/chain/actors/builtin/multisig/message2.go b/chain/actors/builtin/multisig/message2.go new file mode 100644 index 000000000..da2700d06 --- /dev/null +++ b/chain/actors/builtin/multisig/message2.go @@ -0,0 +1,71 @@ +package multisig + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + init2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/init" + multisig2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/multisig" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message2 struct{ message0 } + +func (m message2) Create( + signers []address.Address, threshold uint64, + unlockStart, unlockDuration abi.ChainEpoch, + initialAmount abi.TokenAmount, +) (*types.Message, error) { + + lenAddrs := uint64(len(signers)) + + if lenAddrs < threshold { + return nil, xerrors.Errorf("cannot require signing of more addresses than provided for multisig") + } + + if threshold == 0 { + threshold = lenAddrs + } + + if m.from == address.Undef { + return nil, xerrors.Errorf("must provide source address") + } + + // Set up constructor parameters for multisig + msigParams := &multisig2.ConstructorParams{ + Signers: signers, + NumApprovalsThreshold: threshold, + UnlockDuration: unlockDuration, + StartEpoch: unlockStart, + } + + enc, actErr := actors.SerializeParams(msigParams) + if actErr != nil { + return nil, actErr + } + + // new actors are created by invoking 'exec' on the init actor with the constructor params + execParams := &init2.ExecParams{ + CodeCID: builtin2.MultisigActorCodeID, + ConstructorParams: enc, + } + + enc, actErr = actors.SerializeParams(execParams) + if actErr != nil { + return nil, actErr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Method: builtin2.MethodsInit.Exec, + Params: enc, + Value: initialAmount, + }, nil +} diff --git a/chain/actors/builtin/multisig/multisig.go b/chain/actors/builtin/multisig/state.go similarity index 100% rename from chain/actors/builtin/multisig/multisig.go rename to chain/actors/builtin/multisig/state.go diff --git a/chain/actors/builtin/multisig/v0.go b/chain/actors/builtin/multisig/state0.go similarity index 100% rename from chain/actors/builtin/multisig/v0.go rename to chain/actors/builtin/multisig/state0.go diff --git a/chain/actors/builtin/multisig/v2.go b/chain/actors/builtin/multisig/state2.go similarity index 100% rename from chain/actors/builtin/multisig/v2.go rename to chain/actors/builtin/multisig/state2.go diff --git a/node/impl/full/multisig.go b/node/impl/full/multisig.go index 8c15a27be..715689edc 100644 --- a/node/impl/full/multisig.go +++ b/node/impl/full/multisig.go @@ -9,15 +9,13 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors" - init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/actors/builtin/multisig" "github.com/filecoin-project/lotus/chain/types" builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" - init0 "github.com/filecoin-project/specs-actors/actors/builtin/init" multisig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" "github.com/ipfs/go-cid" - "github.com/minio/blake2b-simd" "go.uber.org/fx" "golang.org/x/xerrors" ) @@ -30,58 +28,31 @@ type MsigAPI struct { MpoolAPI MpoolAPI } +func (a *MsigAPI) messageBuilder(ctx context.Context, from address.Address) (multisig.MessageBuilder, error) { + nver, err := a.StateAPI.StateNetworkVersion(ctx, types.EmptyTSK) + if err != nil { + return nil, err + } + + return multisig.Message(actors.VersionForNetwork(nver), from), nil +} + // TODO: remove gp (gasPrice) from arguments +// TODO: Add "vesting start" to arguments. func (a *MsigAPI) MsigCreate(ctx context.Context, req uint64, addrs []address.Address, duration abi.ChainEpoch, val types.BigInt, src address.Address, gp types.BigInt) (cid.Cid, error) { - lenAddrs := uint64(len(addrs)) - - if lenAddrs < req { - return cid.Undef, xerrors.Errorf("cannot require signing of more addresses than provided for multisig") + mb, err := a.messageBuilder(ctx, src) + if err != nil { + return cid.Undef, err } - if req == 0 { - req = lenAddrs - } - - if src == address.Undef { - return cid.Undef, xerrors.Errorf("must provide source address") - } - - // Set up constructor parameters for multisig - msigParams := &multisig0.ConstructorParams{ - Signers: addrs, - NumApprovalsThreshold: req, - UnlockDuration: duration, - } - - enc, actErr := actors.SerializeParams(msigParams) - if actErr != nil { - return cid.Undef, actErr - } - - // new actors are created by invoking 'exec' on the init actor with the constructor params - // TODO: network upgrade? - execParams := &init0.ExecParams{ - CodeCID: builtin0.MultisigActorCodeID, - ConstructorParams: enc, - } - - enc, actErr = actors.SerializeParams(execParams) - if actErr != nil { - return cid.Undef, actErr - } - - // now we create the message to send this with - msg := types.Message{ - To: init_.Address, - From: src, - Method: builtin0.MethodsInit.Exec, - Params: enc, - Value: val, + msg, err := mb.Create(addrs, req, 0, duration, val) + if err != nil { + return cid.Undef, err } // send the message out to the network - smsg, err := a.MpoolAPI.MpoolPushMessage(ctx, &msg, nil) + smsg, err := a.MpoolAPI.MpoolPushMessage(ctx, msg, nil) if err != nil { return cid.Undef, err } @@ -91,38 +62,14 @@ func (a *MsigAPI) MsigCreate(ctx context.Context, req uint64, addrs []address.Ad func (a *MsigAPI) MsigPropose(ctx context.Context, msig address.Address, to address.Address, amt types.BigInt, src address.Address, method uint64, params []byte) (cid.Cid, error) { - if msig == address.Undef { - return cid.Undef, xerrors.Errorf("must provide a multisig address for proposal") + mb, err := a.messageBuilder(ctx, src) + if err != nil { + return cid.Undef, err } - if to == address.Undef { - return cid.Undef, xerrors.Errorf("must provide a target address for proposal") - } - - if amt.Sign() == -1 { - return cid.Undef, xerrors.Errorf("must provide a positive amount for proposed send") - } - - if src == address.Undef { - return cid.Undef, xerrors.Errorf("must provide source address") - } - - enc, actErr := actors.SerializeParams(&multisig0.ProposeParams{ - To: to, - Value: amt, - Method: abi.MethodNum(method), - Params: params, - }) - if actErr != nil { - return cid.Undef, xerrors.Errorf("failed to serialize parameters: %w", actErr) - } - - msg := &types.Message{ - To: msig, - From: src, - Value: types.NewInt(0), - Method: builtin0.MethodsMultisig.Propose, - Params: enc, + msg, err := mb.Propose(msig, to, amt, abi.MethodNum(method), params) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to create proposal: %w", err) } smsg, err := a.MpoolAPI.MpoolPushMessage(ctx, msg, nil) @@ -200,14 +147,6 @@ func (a *MsigAPI) msigApproveOrCancel(ctx context.Context, operation api.MsigPro return cid.Undef, xerrors.Errorf("must provide multisig address") } - if to == address.Undef { - return cid.Undef, xerrors.Errorf("must provide proposed target address") - } - - if amt.Sign() == -1 { - return cid.Undef, xerrors.Errorf("must provide the positive amount that was proposed") - } - if src == address.Undef { return cid.Undef, xerrors.Errorf("must provide source address") } @@ -220,7 +159,7 @@ func (a *MsigAPI) msigApproveOrCancel(ctx context.Context, operation api.MsigPro proposer = proposerID } - p := multisig0.ProposalHashData{ + p := multisig.ProposalHashData{ Requester: proposer, To: to, Value: amt, @@ -228,42 +167,22 @@ func (a *MsigAPI) msigApproveOrCancel(ctx context.Context, operation api.MsigPro Params: params, } - pser, err := p.Serialize() - if err != nil { - return cid.Undef, err - } - phash := blake2b.Sum256(pser) - - enc, err := actors.SerializeParams(&multisig0.TxnIDParams{ - ID: multisig0.TxnID(txID), - ProposalHash: phash[:], - }) - + mb, err := a.messageBuilder(ctx, src) if err != nil { return cid.Undef, err } - var msigResponseMethod abi.MethodNum - - /* - We pass in a MsigProposeResponse instead of MethodNum to - tighten the possible inputs to just Approve and Cancel. - */ + var msg *types.Message switch operation { case api.MsigApprove: - msigResponseMethod = builtin0.MethodsMultisig.Approve + msg, err = mb.Approve(msig, txID, &p) case api.MsigCancel: - msigResponseMethod = builtin0.MethodsMultisig.Cancel + msg, err = mb.Cancel(msig, txID, &p) default: return cid.Undef, xerrors.Errorf("Invalid operation for msigApproveOrCancel") } - - msg := &types.Message{ - To: msig, - From: src, - Value: types.NewInt(0), - Method: msigResponseMethod, - Params: enc, + if err != nil { + return cid.Undef, err } smsg, err := a.MpoolAPI.MpoolPushMessage(ctx, msg, nil)