diff --git a/app/app.go b/app/app.go index 2c93efb..ec274d5 100644 --- a/app/app.go +++ b/app/app.go @@ -116,6 +116,7 @@ func NewPlasmaMVPChain(logger log.Logger, db dbm.DB, traceStore io.Writer, optio } app.Router().AddRoute(msgs.SpendMsgRoute, handlers.NewSpendHandler(app.dataStore, nextTxIndex, feeUpdater)) app.Router().AddRoute(msgs.IncludeDepositMsgRoute, handlers.NewDepositHandler(app.dataStore, nextTxIndex, plasmaClient)) + app.Router().AddRoute(msgs.ConfirmSigMsgRoute, handlers.NewConfirmSigHandler(app.dataStore)) // custom queriers app.QueryRouter().AddRoute(store.QuerierRouteName, store.NewQuerier(app.dataStore)) diff --git a/cmd/plasmacli/subcmd/tx/broadcast.go b/cmd/plasmacli/subcmd/tx/broadcast.go new file mode 100644 index 0000000..4244ab9 --- /dev/null +++ b/cmd/plasmacli/subcmd/tx/broadcast.go @@ -0,0 +1,106 @@ +package tx + +import ( + "fmt" + "github.com/FourthState/plasma-mvp-sidechain/msgs" + "github.com/FourthState/plasma-mvp-sidechain/plasma" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/ethereum/go-ethereum/rlp" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "strings" +) + +func BroadcastSigsCmd() *cobra.Command { + includeCmd.Flags().Bool(asyncF, false, "wait for transaction commitment synchronously") + return includeCmd +} + +var broadcastSigsCmd = &cobra.Command{ + Use: "broadcast-sigs ", + Short: "Broadcast confirm signatures tied to input", + Long: `Broadcasts confirm signatures tied to an input without spending funds. + Inputs take the format: + (blknum0.txindex0.oindex0.depositnonce0)::(blknum1.txindex1.oindex1.depositnonce1) + + Example usage: + plasmacli broadcast-sigs + plasmacli broadcast-sigs + `, + Args: cobra.MaximumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + viper.BindPFlags(cmd.Flags()) + ctx := context.NewCLIContext() + + // parse inputs + var inputStrs []string + values := args[0] + + inputTokens := strings.Split(strings.TrimSpace(values), "::") + if len(inputTokens) > 2 { + return fmt.Errorf("2 or fewer inputs must be specified") + } + for _, token := range inputTokens { + inputStrs = append(inputStrs, strings.TrimSpace(token)) + } + + // retrieve positions + var positions []plasma.Position + for _, token := range inputStrs { + position, err := plasma.FromPositionString(token) + if err != nil { + return fmt.Errorf("error parsing position from string: %s", err) + } + positions = append(positions, position) + } + + // get confirmation signatures from local storage + confirmSignatures := getConfirmSignatures(positions) + + // build transaction + // create non-nil inputs with null signatures + input1 := plasma.Input{} + if len(positions) >= 1 { + input1.Position = positions[0] + input1.Signature = [65]byte{1} + input1.ConfirmSignatures = confirmSignatures[0] + } + + input2 := plasma.Input{} + if len(positions) >= 2 { + input2.Position = positions[1] + input2.Signature = [65]byte{1} + input2.ConfirmSignatures = confirmSignatures[1] + } + + cmd.SilenceUsage = true + + msg := msgs.ConfirmSigMsg{ + Input1: input1, + Input2: input2, + } + if err := msg.ValidateBasic(); err != nil { + return fmt.Errorf("failed on validating inputs. If you didn't provide the inputs please open an issue on github. error: %s", err) + } + + txBytes, err := rlp.EncodeToBytes(&msg) + if err != nil { + return err + } + + // broadcast to the node + if viper.GetBool(asyncF) { + if _, err := ctx.BroadcastTxAsync(txBytes); err != nil { + return err + } + } else { + res, err := ctx.BroadcastTxAndAwaitCommit(txBytes) + if err != nil { + return err + } + fmt.Printf("Committed at block %d. Hash %s\n", res.Height, res.TxHash) + } + + return nil + }, +} diff --git a/cmd/plasmacli/subcmd/tx/root.go b/cmd/plasmacli/subcmd/tx/root.go index 032c5c2..8e6235e 100644 --- a/cmd/plasmacli/subcmd/tx/root.go +++ b/cmd/plasmacli/subcmd/tx/root.go @@ -25,6 +25,7 @@ func RootCmd() *cobra.Command { IncludeCmd(), SpendCmd(), SignCmd(), + BroadcastSigsCmd(), ) return txCmd diff --git a/cmd/plasmacli/subcmd/tx/spend.go b/cmd/plasmacli/subcmd/tx/spend.go index 16876fe..4ece7c2 100644 --- a/cmd/plasmacli/subcmd/tx/spend.go +++ b/cmd/plasmacli/subcmd/tx/spend.go @@ -242,7 +242,6 @@ func parseConfirmSignatures(confirmSignatures [2][][65]byte) ([2][][65]byte, err flag = confirmSigs0F } else { flag = confirmSigs1F - } confirmSigTokens := strings.Split(strings.TrimSpace(viper.GetString(flag)), ",") // empty confirmsig diff --git a/handlers/anteHandler.go b/handlers/anteHandler.go index 8e30a48..a265794 100644 --- a/handlers/anteHandler.go +++ b/handlers/anteHandler.go @@ -34,6 +34,9 @@ func NewAnteHandler(ds store.DataStore, client plasmaConn) sdk.AnteHandler { case "spend_utxo": spendMsg := msg.(msgs.SpendMsg) return spendMsgAnteHandler(ctx, ds, spendMsg, client) + case "confirm_sig": + confirmSigMsg := msg.(msgs.ConfirmSigMsg) + return confirmSigAnteHandler(ctx, ds, confirmSigMsg, client) default: return ctx, ErrInvalidTransaction("msg is not of type SpendMsg or IncludeDepositMsg").Result(), true } @@ -58,7 +61,7 @@ func spendMsgAnteHandler(ctx sdk.Context, ds store.DataStore, spendMsg msgs.Spen /* validate inputs */ for i, signer := range signers { - amt, res := validateInput(ctx, ds, spendMsg.Inputs[i], common.BytesToAddress(signer), client) + amt, res := validateSpendInput(ctx, ds, spendMsg.Inputs[i], common.BytesToAddress(signer), client) if !res.IsOK() { return ctx, res, true } @@ -85,7 +88,7 @@ func spendMsgAnteHandler(ctx sdk.Context, ds store.DataStore, spendMsg msgs.Spen } // validates the inputs against the output store and returns the amount of the respective input -func validateInput(ctx sdk.Context, ds store.DataStore, input plasma.Input, signer common.Address, client plasmaConn) (*big.Int, sdk.Result) { +func validateSpendInput(ctx sdk.Context, ds store.DataStore, input plasma.Input, signer common.Address, client plasmaConn) (*big.Int, sdk.Result) { var amt *big.Int // inputUTXO must be owned by the signer due to the prefix so we do not need to @@ -185,3 +188,60 @@ func includeDepositAnteHandler(ctx sdk.Context, ds store.DataStore, msg msgs.Inc } return ctx, sdk.Result{}, false } + +// validates that the confirmSig msg is valid given the current plasma sidechain state +func confirmSigAnteHandler(ctx sdk.Context, ds store.DataStore, confirmSigMsg msgs.ConfirmSigMsg, client plasmaConn) (newCtx sdk.Context, res sdk.Result, abort bool) { + + res = validateConfirmSigMsgInput(ctx, ds, confirmSigMsg.Input1, client) + if !res.IsOK() { + return ctx, res, true + } + + res = validateConfirmSigMsgInput(ctx, ds, confirmSigMsg.Input2, client) + if !res.IsOK() { + return ctx, res, true + } + + return ctx, sdk.Result{}, false +} + +// validates the inputs against the output store +func validateConfirmSigMsgInput(ctx sdk.Context, ds store.DataStore, input plasma.Input, client plasmaConn) sdk.Result { + + if input.Signature == [65]byte{} { + return sdk.Result{} + } + if input.Signature != [65]byte{1} { + return ErrInvalidSignature("invalid signature provided").Result() + } + + _, ok := ds.GetOutput(ctx, input.Position) + if !ok { + return ErrInvalidInput("input, %v, does not exist", input.Position).Result() + } + + // validate inputs/confirmation signatures if not a fee utxo or deposit utxo + if !input.IsDeposit() && !input.IsFee() { + tx, ok := ds.GetTxWithPosition(ctx, input.Position) + if !ok { + return sdk.ErrInternal(fmt.Sprintf("failed to retrieve the transaction that input with position %s belongs to", input.Position)).Result() + } + + res := validateConfirmSignatures(ctx, ds, input, tx) + if !res.IsOK() { + return res + } + + // check if the parent utxo has exited + for _, in := range tx.Transaction.Inputs { + exited, err := client.HasTxExited(ds.PlasmaBlockHeight(ctx), in.Position) + if err != nil { + return ErrInvalidInput(fmt.Sprintf("failed to retrieve exit information on input, %v", in.Position)).Result() + } else if exited { + return ErrExitedInput(fmt.Sprintf("a parent of the input has exited. Position: %v", in.Position)).Result() + } + } + } + + return sdk.Result{} +} \ No newline at end of file diff --git a/handlers/anteHandler_test.go b/handlers/anteHandler_test.go index a9dcc78..76b9f8b 100644 --- a/handlers/anteHandler_test.go +++ b/handlers/anteHandler_test.go @@ -519,6 +519,135 @@ func TestAnteDepositWrongOwner(t *testing.T) { require.True(t, abort, "Wrong owner deposit inclusion did not abort") } +/*=====================================================================================================================================*/ +// ConfirmSigMsg Antehandler tests + +func TestAnteConfirmSigInvalidSig(t *testing.T) { + // setup + ctx, ds := setup() + handler := NewAnteHandler(ds, conn{}) + + // Try to include with invalid sig + msg := msgs.ConfirmSigMsg{ + Input1: plasma.NewInput(plasma.NewPosition(utils.Big1, 1, 0, utils.Big0), [65]byte{87}, [][65]byte{}), + Input2:plasma.NewInput(plasma.NewPosition(utils.Big1, 2, 1, utils.Big0), [65]byte{46}, [][65]byte{}), + } + + _, res, abort := handler(ctx, msg, false) + require.False(t, res.IsOK(), "Invalid input signature did not error") + require.True(t, abort, "Invalid input signature did not abort") +} + +func TestAnteConfirmSigInputDNE(t *testing.T) { + //setup + ctx, ds := setup() + handler := NewAnteHandler(ds, conn{}) + + // Input will by default not exist + msg := msgs.ConfirmSigMsg{ + Input1: plasma.NewInput(plasma.NewPosition(utils.Big1, 1, 0, utils.Big0), [65]byte{1}, nil), + Input2:plasma.NewInput(plasma.NewPosition(utils.Big1, 2, 1, utils.Big0), [65]byte{1}, nil), + } + + _, res, abort := handler(ctx, msg, false) + require.False(t, res.IsOK(), "Invalid input did not error") + require.True(t, abort, "Invalid input did not abort") +} + +func TestAnteConfirmSigEmptyConfirmSig(t *testing.T) { + // setup + ctx, ds := setup() + handler := NewAnteHandler(ds, conn{}) + + pos := plasma.NewPosition(utils.Big1, 2, 3, utils.Big0) + transaction := plasma.Transaction{ + Inputs: []plasma.Input{plasma.NewInput(pos, [65]byte{}, nil)}, + Outputs: []plasma.Output{plasma.NewOutput(addr, big.NewInt(10))}, + Fee: utils.Big0, + } + storeTransaction := store.Transaction{ + Transaction: transaction, + ConfirmationHash: []byte{1234}, + Spent: []bool{false}, + SpenderTxs: [][]byte{}, + Position: pos, + } + ds.StoreOutputs(ctx, storeTransaction) + + // Try to include with empty confirm sig + msg := msgs.ConfirmSigMsg{ + Input1: plasma.NewInput(pos, [65]byte{1}, nil), + Input2: plasma.Input{}, + } + + _, res, abort := handler(ctx, msg, false) + require.False(t, res.IsOK(), "Empty confirm signature did not error") + require.True(t, abort, "Empty confirm signature did not abort") +} + +func TestAnteConfirmSigInputIsDeposit(t *testing.T) { + // setup + ctx, ds := setup() + handler := NewAnteHandler(ds, conn{}) + + pos := plasma.NewPosition(utils.Big0, 0, 0, utils.Big1) + transaction := plasma.Transaction{ + Inputs: []plasma.Input{plasma.NewInput(pos, [65]byte{}, nil)}, + Outputs: []plasma.Output{plasma.NewOutput(addr, big.NewInt(10))}, + Fee: utils.Big0, + } + storeTransaction := store.Transaction{ + Transaction: transaction, + ConfirmationHash: []byte{1234}, + Spent: []bool{false}, + SpenderTxs: [][]byte{}, + Position: pos, + } + ds.StoreOutputs(ctx, storeTransaction) + + // Try to include with deposit input + msg := msgs.ConfirmSigMsg{ + Input1: plasma.NewInput(pos, [65]byte{1}, [][65]byte{{123}}), + Input2: plasma.Input{}, + } + + _, res, abort := handler(ctx, msg, false) + require.True(t, res.IsOK(), "Deposit message errored") + require.False(t, abort, "Deposit message aborted") +} + +func TestAnteConfirmSigInputNotPartOfTx(t *testing.T) { + // setup + ctx, ds := setup() + handler := NewAnteHandler(ds, conn{}) + + pos := plasma.NewPosition(utils.Big1, 2, 3, utils.Big0) + transaction := plasma.Transaction{ + Inputs: []plasma.Input{plasma.NewInput(pos, [65]byte{}, nil)}, + Outputs: []plasma.Output{plasma.NewOutput(addr, big.NewInt(10))}, + Fee: utils.Big0, + } + storeTransaction := store.Transaction{ + Transaction: transaction, + ConfirmationHash: []byte{1234}, + Spent: []bool{false}, + SpenderTxs: [][]byte{}, + Position: pos, + } + ds.StoreOutputs(ctx, storeTransaction) + + // Try to include with tx not stored yet (default behaviour) + msg := msgs.ConfirmSigMsg{ + Input1: plasma.NewInput(plasma.NewPosition(utils.Big1, 2, 3, utils.Big0), [65]byte{1}, [][65]byte{{123}}), + Input2: plasma.Input{}, + } + + _, res, abort := handler(ctx, msg, false) + require.True(t, res.IsOK(), "Deposit message errored") + require.False(t, abort, "Deposit message aborted") +} + + func setupDeposits(ctx sdk.Context, ds store.DataStore, inputs ...Deposit) { for _, i := range inputs { deposit := plasma.Deposit{ diff --git a/handlers/confirmSigHandler.go b/handlers/confirmSigHandler.go new file mode 100644 index 0000000..765bfa3 --- /dev/null +++ b/handlers/confirmSigHandler.go @@ -0,0 +1,55 @@ +package handlers + +import ( + "github.com/FourthState/plasma-mvp-sidechain/msgs" + "github.com/FourthState/plasma-mvp-sidechain/store" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// NewSpendHandler sets the inputs of a spend msg to spent and creates new +// outputs that are added to the data store. +func NewConfirmSigHandler(ds store.DataStore) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + confirmSigMsg, ok := msg.(msgs.ConfirmSigMsg) + if !ok { + panic("Msg does not implement SpendMsg") + } + + /* Get tx, update tx data with confirm sigs, and store updated tx object */ + // TODO: Validation on correctness of confirm sigs? + tx1, ok := ds.GetTxWithPosition(ctx, confirmSigMsg.Input1.Position) + if !ok { + panic("no transaction exists for the position provided") + } + + for i := 0; i < len(tx1.Transaction.Inputs); i++ { + // TODO: Is TxIndex sufficient? Also should likely check output index? + if tx1.Transaction.Inputs[i].Position.TxIndex != confirmSigMsg.Input1.Position.TxIndex { + continue + } + + // TODO: How to update ConfirmSignatures? Only 1 confirm sig held in msg, tx.inputs[i].confirmsignatures supports array + tx1.Transaction.Inputs[i].ConfirmSignatures = confirmSigMsg.Input1.ConfirmSignatures + } + + ds.StoreTx(ctx, tx1) + + tx2, ok := ds.GetTxWithPosition(ctx, confirmSigMsg.Input2.Position) + if !ok { + panic("no transaction exists for the position provided") + } + + for i := 0; i < len(tx2.Transaction.Inputs); i++ { + if tx2.Transaction.Inputs[i].Position.TxIndex != confirmSigMsg.Input2.Position.TxIndex { + continue + } + + // TODO: How to update ConfirmSignatures? Only 1 confirm sig held in msg, tx.inputs[i].confirmsignatures supports array + tx2.Transaction.Inputs[i].ConfirmSignatures = confirmSigMsg.Input2.ConfirmSignatures + } + + ds.StoreTx(ctx, tx2) + + return sdk.Result{} + } +} diff --git a/handlers/confirmSigHandler_test.go b/handlers/confirmSigHandler_test.go new file mode 100644 index 0000000..9c36bdc --- /dev/null +++ b/handlers/confirmSigHandler_test.go @@ -0,0 +1,63 @@ +package handlers + +import ( + "github.com/FourthState/plasma-mvp-sidechain/msgs" + "github.com/FourthState/plasma-mvp-sidechain/plasma" + "github.com/FourthState/plasma-mvp-sidechain/store" + "github.com/FourthState/plasma-mvp-sidechain/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "math/big" + "testing" +) + +func TestIncludeConfirmSig(t *testing.T) { + // blockStore is at next block height 1 + ctx, ds := setup() + + // Give deposit a cooked connection that will always provide deposit with given position + confirmSigHandler := NewConfirmSigHandler(ds) + + // Add tx with empty confirm sig into ds + pos1 := plasma.NewPosition(utils.Big1, 1, 2, utils.Big0) + pos2 := plasma.NewPosition(utils.Big1, 2, 3, utils.Big0) + newOwner := common.HexToAddress("1") + transaction := plasma.Transaction{ + Inputs: []plasma.Input{plasma.NewInput(pos1, [65]byte{}, nil), plasma.NewInput(pos2, [65]byte{}, nil)}, + Outputs: []plasma.Output{plasma.NewOutput(newOwner, big.NewInt(10)), plasma.NewOutput(newOwner, big.NewInt(10))}, + Fee: utils.Big0, + } + storeTransaction1 := store.Transaction{ + Transaction: transaction, + ConfirmationHash: []byte{1234}, + Spent: []bool{false}, + SpenderTxs: [][]byte{}, + Position: pos1, + } + storeTransaction2 := store.Transaction{ + Transaction: transaction, + ConfirmationHash: []byte{5678}, + Spent: []bool{false}, + SpenderTxs: [][]byte{}, + Position: pos2, + } + + ds.StoreTx(ctx, storeTransaction1) + ds.StoreTx(ctx, storeTransaction2) + + // create a msg that populates confirm sig fields in ds + input1 := plasma.Input{Position: pos1, Signature: [65]byte{1}, ConfirmSignatures: [][65]byte{{1}}} + input2 := plasma.Input{Position: pos2, Signature: [65]byte{1}, ConfirmSignatures: [][65]byte{{1}}} + msg := msgs.ConfirmSigMsg{ + Input1: input1, + Input2: input2, + } + + confirmSigHandler(ctx, msg) + tx1, ok := ds.GetTx(ctx, []byte{1234}) + tx2, ok := ds.GetTx(ctx, []byte{5678}) + + require.True(t, ok, "tx does not exist in store") + require.Equal(t, input1.ConfirmSignatures, tx1.Transaction.Inputs[0].ConfirmSignatures, "confirm sigs not populated") + require.Equal(t, input2.ConfirmSignatures, tx2.Transaction.Inputs[1].ConfirmSignatures, "confirm sigs not populated") +} \ No newline at end of file diff --git a/msgs/confirmSigMsg.go b/msgs/confirmSigMsg.go new file mode 100644 index 0000000..671d9b2 --- /dev/null +++ b/msgs/confirmSigMsg.go @@ -0,0 +1,52 @@ +package msgs + +import ( + "github.com/FourthState/plasma-mvp-sidechain/plasma" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + // ConfirmSigMsgRoute is used for routing this message. + ConfirmSigMsgRoute = "confirmSig" +) + +// SpendMsg implements the RLP interface through `Transaction` +type ConfirmSigMsg struct { + Input1 plasma.Input + Input2 plasma.Input +} + +// Type implements the sdk.Msg interface. +func (msg ConfirmSigMsg) Type() string { return "confirm_sig" } + +// Route implements the sdk.Msg interface. +func (msg ConfirmSigMsg) Route() string { return ConfirmSigMsgRoute } + +// GetSigners will attempt to retrieve the signers of the message. +// CONTRACT: a nil slice is returned if recovery fails +func (msg ConfirmSigMsg) GetSigners() []sdk.AccAddress { + return nil +} + +// GetSignBytes returns the Keccak256 hash of the transaction. +func (msg ConfirmSigMsg) GetSignBytes() []byte { + return nil +} + +// ValidateBasic verifies that the transaction is valid. +func (msg ConfirmSigMsg) ValidateBasic() sdk.Error { + if err := msg.Input1.ValidateBasic(); err != nil { + return ErrInvalidConfirmSigMsg(DefaultCodespace, err.Error()) + } + + if err := msg.Input2.ValidateBasic(); err != nil { + return ErrInvalidConfirmSigMsg(DefaultCodespace, err.Error()) + } + + return nil +} + +// GetMsgs implements the sdk.Tx interface +func (msg ConfirmSigMsg) GetMsgs() []sdk.Msg { + return []sdk.Msg{msg} +} \ No newline at end of file diff --git a/msgs/confirmSigMsg_test.go b/msgs/confirmSigMsg_test.go new file mode 100644 index 0000000..889256c --- /dev/null +++ b/msgs/confirmSigMsg_test.go @@ -0,0 +1,62 @@ +package msgs + +import ( + "fmt" + "github.com/FourthState/plasma-mvp-sidechain/plasma" + "github.com/FourthState/plasma-mvp-sidechain/utils" + "github.com/ethereum/go-ethereum/rlp" + "github.com/stretchr/testify/require" + "reflect" + "testing" +) + +func TestConfirmSigMsgValidate(t *testing.T) { + type confirmSigCase struct { + input1 plasma.Input + input2 plasma.Input + } + + invalidCases := []confirmSigCase{ + // nil position non-nil sig + {plasma.NewInput(plasma.Position{}, [65]byte{100}, nil), plasma.NewInput(plasma.Position{}, [65]byte{}, nil)}, + // nil position non-nil confirm sig + {plasma.NewInput(plasma.Position{}, [65]byte{}, nil), plasma.NewInput(plasma.Position{}, [65]byte{}, [][65]byte{{1}})}, + // invalid position + {plasma.NewInput(plasma.Position{ BlockNum: utils.Big1, TxIndex: 1, OutputIndex: 1, DepositNonce: utils.Big1}, [65]byte{1}, [][65]byte{{1}}), + plasma.NewInput(plasma.Position{BlockNum: utils.Big1, TxIndex: 1, OutputIndex: 1, DepositNonce: utils.Big1}, [65]byte{}, nil)}, + // valid deposit empty sig + {plasma.NewInput(plasma.Position{BlockNum: utils.Big0, TxIndex: 0, OutputIndex: 0, DepositNonce:utils.Big1}, [65]byte{}, nil), + plasma.NewInput(plasma.Position{BlockNum: utils.Big0, TxIndex: 0, OutputIndex: 0, DepositNonce:utils.Big2}, [65]byte{}, nil)}, + // valid deposit non-nil confirm sig + {plasma.NewInput(plasma.Position{BlockNum: utils.Big0, TxIndex: 0, OutputIndex: 0, DepositNonce:utils.Big1}, [65]byte{1}, [][65]byte{{1}}), + plasma.NewInput(plasma.Position{BlockNum: utils.Big0, TxIndex: 0, OutputIndex: 0, DepositNonce:utils.Big2}, [65]byte{1}, [][65]byte{{1}})}, + // valid output empty confirm sig + {plasma.NewInput(plasma.Position{BlockNum: utils.Big1, TxIndex: 1, OutputIndex: 1, DepositNonce:utils.Big0}, [65]byte{1}, [][65]byte{{1}}), + plasma.NewInput(plasma.Position{BlockNum: utils.Big1, TxIndex: 2, OutputIndex: 1, DepositNonce:utils.Big0}, [65]byte{1}, nil)}, + } + + + for i, c := range invalidCases { + confirmSigMsg := ConfirmSigMsg{ + Input1: c.input1, + Input2: c.input2, + } + require.NotNil(t, confirmSigMsg.ValidateBasic(), fmt.Sprintf("Testcase %d failed", i)) + } +} + +func TestConfirmSigMsgSerialization(t *testing.T) { + msg := ConfirmSigMsg{ + //Input1: plasma.NewInput(), + //Input2: plasma.NewInput(), + } + + bytes, err := rlp.EncodeToBytes(&msg) + require.NoError(t, err, "serialization error") + + tx, err := TxDecoder(bytes) + + require.NoError(t, err, "deserialization error") + + require.True(t, reflect.DeepEqual(msg, tx), "serialized and deserialized msgs not equal") +} \ No newline at end of file diff --git a/msgs/errors.go b/msgs/errors.go index 99bebe7..dc2b6bd 100644 --- a/msgs/errors.go +++ b/msgs/errors.go @@ -10,6 +10,7 @@ const ( CodeInvalidSpendMsg sdk.CodeType = 1 CodeInvalidIncludeDepositMsg sdk.CodeType = 2 + CodeInvalidConfirmSigMsg sdk.CodeType = 3 ) // ErrInvalidSpendMsg error for an invalid spend msg @@ -21,3 +22,7 @@ func ErrInvalidSpendMsg(codespace sdk.CodespaceType, msg string, args ...interfa func ErrInvalidIncludeDepositMsg(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { return sdk.NewError(codespace, CodeInvalidIncludeDepositMsg, msg, args...) } + +func ErrInvalidConfirmSigMsg(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { + return sdk.NewError(codespace, CodeInvalidConfirmSigMsg, msg, args...) +} \ No newline at end of file diff --git a/msgs/txDecoder.go b/msgs/txDecoder.go index a6795ff..68fb51e 100644 --- a/msgs/txDecoder.go +++ b/msgs/txDecoder.go @@ -7,17 +7,20 @@ import ( ) // TxDecoder attempts to RLP decode the transaction bytes into a SpendMsg first -// then to a IncludeDepositMsg otherwise returns an error. +// then to a IncludeDepositMsg then to a ConfirmSigMsg otherwise returns an error. func TxDecoder(txBytes []byte) (sdk.Tx, sdk.Error) { var spendMsg SpendMsg if err := rlp.DecodeBytes(txBytes, &spendMsg); err != nil { var depositMsg IncludeDepositMsg if err2 := rlp.DecodeBytes(txBytes, &depositMsg); err2 != nil { - return nil, sdk.ErrTxDecode(fmt.Sprintf("decode to SpendMsg: %s. Decode to DepositMsg: %s", - err.Error(), err2.Error())) + var confirmSigMsg ConfirmSigMsg + if err3 := rlp.DecodeBytes(txBytes, &confirmSigMsg); err3 != nil { + return nil, sdk.ErrTxDecode(fmt.Sprintf("Decode to SpendMsg error: %s , Decode to DepositMsg error: %s, Decode to ConfirmSigMsg error: %s", + err.Error(), err2.Error(), err3.Error())) + } + return confirmSigMsg, nil } return depositMsg, nil } - return spendMsg, nil }