From b470d79bc456c699b501b2c48ffe8401202cc378 Mon Sep 17 00:00:00 2001 From: Wesley Graham Date: Sun, 8 Mar 2020 15:26:37 -0700 Subject: [PATCH 1/7] Add broadcast confirm sig logic --- app/app.go | 1 + cmd/plasmacli/subcmd/tx/broadcast.go | 143 +++++++++++++++++++++++++++ cmd/plasmacli/subcmd/tx/root.go | 1 + handlers/anteHandler.go | 81 ++++++++++++++- handlers/confirmSigHandler.go | 28 ++++++ handlers/confirmSigHandler_test.go | 1 + msgs/confirmSigMsg.go | 74 ++++++++++++++ msgs/confirmSigMsg_test.go | 1 + msgs/errors.go | 5 + 9 files changed, 333 insertions(+), 2 deletions(-) create mode 100644 cmd/plasmacli/subcmd/tx/broadcast.go create mode 100644 handlers/confirmSigHandler.go create mode 100644 handlers/confirmSigHandler_test.go create mode 100644 msgs/confirmSigMsg.go create mode 100644 msgs/confirmSigMsg_test.go diff --git a/app/app.go b/app/app.go index 2c93efb..e91489a 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, nextTxIndex, feeUpdater)) // 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..69b799f --- /dev/null +++ b/cmd/plasmacli/subcmd/tx/broadcast.go @@ -0,0 +1,143 @@ +package tx + +import ( +"fmt" +clistore "github.com/FourthState/plasma-mvp-sidechain/cmd/plasmacli/store" +"github.com/FourthState/plasma-mvp-sidechain/msgs" +"github.com/FourthState/plasma-mvp-sidechain/plasma" +"github.com/FourthState/plasma-mvp-sidechain/utils" +"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", + Long: `Broadcasts confirm signatures to network without spending funds. must take form of account +name, format: acc1::acc2. Inputs are UTXO Positions to be spent, format: (blknum0.txindex0.oindex0.depositnonce0)::(blknum1.txindex1.oindex1.depositnonce1) + + Example usage: + plasmacli broadcast-sigs + plasmacli broadcast-sigs + plasmacli broadcast-sigs --confirmSigs0 -confirmSigs1 + `, + Args: cobra.MaximumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + viper.BindPFlags(cmd.Flags()) + ctx := context.NewCLIContext() + + // parse accounts + var accs []string + names := args[0] + + accTokens := strings.Split(strings.TrimSpace(names), "::") + if len(accTokens) == 0 || len(accTokens) > 2 { + return fmt.Errorf("1 or 2 accounts must be specified") + } + for _, token := range accTokens { + accs = append(accs, strings.TrimSpace(token)) + } + + // parse inputs + var inputStrs []string + values := args[1] + + 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)) + } + + 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) + + // override retrieved signatures if provided through flags - check if parseConfirmSignatures is right tool + confirmSignatures, err := parseConfirmSignatures(confirmSignatures) + if err != nil { + return fmt.Errorf("error retrieving confirm Signatures %s", err) + } + + // build transaction + // create non-nil inputs with signatures + input1 := plasma.Input{} + if len(positions) > 0 { + signer := accs[0] + positionHash := utils.ToEthSignedMessageHash(positions[0].Bytes()) + var signature [65]byte + sig, err := clistore.SignHashWithPassphrase(signer, positionHash) + if err != nil { + return err + } + copy(signature[:], sig) + input1.Position = positions[0] + input1.Signature = signature + input1.ConfirmSignatures = confirmSignatures[0] + } + + input2 := plasma.Input{} + if len(positions) > 1 { + signer := accs[1] + positionHash := utils.ToEthSignedMessageHash(positions[1].Bytes()) + var signature [65]byte + sig, err := clistore.SignHashWithPassphrase(signer, positionHash) + if err != nil { + return err + } + copy(signature[:], sig) + + input2.Position = positions[1] + input2.Signature = signature + 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/handlers/anteHandler.go b/handlers/anteHandler.go index 8e30a48..63b8101 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,77 @@ 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) { + + // attempt to recover signers + signers := confirmSigMsg.GetSigners() + if len(signers) == 0 { + return ctx, ErrInvalidTransaction("failed recovering signers").Result(), true + } + + expectedSigners := 0 + if confirmSigMsg.Input1.Signature != [65]byte{} { + expectedSigners += 1 + } + if confirmSigMsg.Input2.Signature != [65]byte{} { + expectedSigners += 1 + } + + if len(signers) != expectedSigners { + return ctx, ErrInvalidSignature("number of signers does not equal number of signatures").Result(), true + } + + res = validateSigMsgInput(ctx, ds, confirmSigMsg.Input1, common.BytesToAddress(signers[0]), client) + if !res.IsOK() { + return ctx, res, true + } + if len(signers) > 1 { + res = validateSigMsgInput(ctx, ds, confirmSigMsg.Input2, common.BytesToAddress(signers[1]), client) + if !res.IsOK() { + return ctx, res, true + } + } + + return ctx, sdk.Result{}, false +} + +// validates the inputs against the output store and returns the amount of the respective input +func validateSigMsgInput(ctx sdk.Context, ds store.DataStore, input plasma.Input, signer common.Address, client plasmaConn) (sdk.Result) { + + // inputUTXO must be owned by the signer due to the prefix so we do not need to + // check the owner of the position + inputUTXO, ok := ds.GetOutput(ctx, input.Position) + if !ok { + return ErrInvalidInput("input, %v, does not exist", input.Position).Result() + } + if !bytes.Equal(inputUTXO.Output.Owner[:], signer[:]) { + return ErrSignatureVerificationFailure(fmt.Sprintf("transaction was not signed by correct address. Got: 0x%x. Expected: 0x%x", signer, inputUTXO.Output.Owner)).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/confirmSigHandler.go b/handlers/confirmSigHandler.go new file mode 100644 index 0000000..3f62575 --- /dev/null +++ b/handlers/confirmSigHandler.go @@ -0,0 +1,28 @@ +package handlers + +import ( + "crypto/sha256" + "github.com/FourthState/plasma-mvp-sidechain/msgs" + "github.com/FourthState/plasma-mvp-sidechain/plasma" + "github.com/FourthState/plasma-mvp-sidechain/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "math/big" +) + +// 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, nextTxIndex NextTxIndex, feeUpdater FeeUpdater) 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") + } + + /* Store Transaction and create new outputs */ + // TODO: Find best way to store sigs/inputs in DS + // input1 := store.TxInput{} + // input2 := store.TxInput{} + // ds.StoreInput(ctx, input1) + return sdk.Result{} + } +} diff --git a/handlers/confirmSigHandler_test.go b/handlers/confirmSigHandler_test.go new file mode 100644 index 0000000..5ac8282 --- /dev/null +++ b/handlers/confirmSigHandler_test.go @@ -0,0 +1 @@ +package handlers diff --git a/msgs/confirmSigMsg.go b/msgs/confirmSigMsg.go new file mode 100644 index 0000000..20b2ab2 --- /dev/null +++ b/msgs/confirmSigMsg.go @@ -0,0 +1,74 @@ +package msgs + +import ( + "github.com/FourthState/plasma-mvp-sidechain/plasma" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/FourthState/plasma-mvp-sidechain/utils" + "github.com/ethereum/go-ethereum/crypto" +) + +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 { + positionHash1 := utils.ToEthSignedMessageHash(msg.Input1.Position.Bytes()) + var addrs []sdk.AccAddress + + // recover first owner + pubKey, err := crypto.SigToPub(positionHash1, msg.Input1.Signature[:]) + if err != nil { + return nil + } + addrs = append(addrs, sdk.AccAddress(crypto.PubkeyToAddress(*pubKey).Bytes())) + + if msg.Input2.Signature != [65]byte{} { + // recover the second owner + positionHash2 := utils.ToEthSignedMessageHash(msg.Input1.Position.Bytes()) + pubKey, err = crypto.SigToPub(positionHash2, msg.Input2.Signature[:]) + if err != nil { + return nil + } + addrs = append(addrs, sdk.AccAddress(crypto.PubkeyToAddress(*pubKey).Bytes())) + } + + return addrs +} + +// 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..c94c858 --- /dev/null +++ b/msgs/confirmSigMsg_test.go @@ -0,0 +1 @@ +package msgs 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 From a3c1744e47a6a41c19a9139ccb80a36abdee2c6e Mon Sep 17 00:00:00 2001 From: Wesley Graham Date: Mon, 16 Mar 2020 21:55:48 -0700 Subject: [PATCH 2/7] Remove signature over msg --- cmd/plasmacli/subcmd/tx/broadcast.go | 79 ++++++++-------------------- cmd/plasmacli/subcmd/tx/spend.go | 1 - handlers/anteHandler.go | 38 +++---------- msgs/confirmSigMsg.go | 24 +-------- 4 files changed, 29 insertions(+), 113 deletions(-) diff --git a/cmd/plasmacli/subcmd/tx/broadcast.go b/cmd/plasmacli/subcmd/tx/broadcast.go index 69b799f..4244ab9 100644 --- a/cmd/plasmacli/subcmd/tx/broadcast.go +++ b/cmd/plasmacli/subcmd/tx/broadcast.go @@ -1,16 +1,14 @@ package tx import ( -"fmt" -clistore "github.com/FourthState/plasma-mvp-sidechain/cmd/plasmacli/store" -"github.com/FourthState/plasma-mvp-sidechain/msgs" -"github.com/FourthState/plasma-mvp-sidechain/plasma" -"github.com/FourthState/plasma-mvp-sidechain/utils" -"github.com/cosmos/cosmos-sdk/client/context" -"github.com/ethereum/go-ethereum/rlp" -"github.com/spf13/cobra" -"github.com/spf13/viper" -"strings" + "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 { @@ -19,36 +17,24 @@ func BroadcastSigsCmd() *cobra.Command { } var broadcastSigsCmd = &cobra.Command{ - Use: "broadcast-sigs ", - Short: "Broadcast confirm signatures", - Long: `Broadcasts confirm signatures to network without spending funds. must take form of account -name, format: acc1::acc2. Inputs are UTXO Positions to be spent, format: (blknum0.txindex0.oindex0.depositnonce0)::(blknum1.txindex1.oindex1.depositnonce1) + 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 - plasmacli broadcast-sigs --confirmSigs0 -confirmSigs1 `, Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { viper.BindPFlags(cmd.Flags()) ctx := context.NewCLIContext() - // parse accounts - var accs []string - names := args[0] - - accTokens := strings.Split(strings.TrimSpace(names), "::") - if len(accTokens) == 0 || len(accTokens) > 2 { - return fmt.Errorf("1 or 2 accounts must be specified") - } - for _, token := range accTokens { - accs = append(accs, strings.TrimSpace(token)) - } - // parse inputs var inputStrs []string - values := args[1] + values := args[0] inputTokens := strings.Split(strings.TrimSpace(values), "::") if len(inputTokens) > 2 { @@ -58,6 +44,7 @@ name, format: acc1::acc2. Inputs are UTXO Positions to be spent, format: (blknum inputStrs = append(inputStrs, strings.TrimSpace(token)) } + // retrieve positions var positions []plasma.Position for _, token := range inputStrs { position, err := plasma.FromPositionString(token) @@ -70,42 +57,19 @@ name, format: acc1::acc2. Inputs are UTXO Positions to be spent, format: (blknum // get confirmation signatures from local storage confirmSignatures := getConfirmSignatures(positions) - // override retrieved signatures if provided through flags - check if parseConfirmSignatures is right tool - confirmSignatures, err := parseConfirmSignatures(confirmSignatures) - if err != nil { - return fmt.Errorf("error retrieving confirm Signatures %s", err) - } - // build transaction - // create non-nil inputs with signatures + // create non-nil inputs with null signatures input1 := plasma.Input{} - if len(positions) > 0 { - signer := accs[0] - positionHash := utils.ToEthSignedMessageHash(positions[0].Bytes()) - var signature [65]byte - sig, err := clistore.SignHashWithPassphrase(signer, positionHash) - if err != nil { - return err - } - copy(signature[:], sig) + if len(positions) >= 1 { input1.Position = positions[0] - input1.Signature = signature + input1.Signature = [65]byte{1} input1.ConfirmSignatures = confirmSignatures[0] } input2 := plasma.Input{} - if len(positions) > 1 { - signer := accs[1] - positionHash := utils.ToEthSignedMessageHash(positions[1].Bytes()) - var signature [65]byte - sig, err := clistore.SignHashWithPassphrase(signer, positionHash) - if err != nil { - return err - } - copy(signature[:], sig) - + if len(positions) >= 2 { input2.Position = positions[1] - input2.Signature = signature + input2.Signature = [65]byte{1} input2.ConfirmSignatures = confirmSignatures[1] } @@ -116,7 +80,7 @@ name, format: acc1::acc2. Inputs are UTXO Positions to be spent, format: (blknum 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) + 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) @@ -140,4 +104,3 @@ name, format: acc1::acc2. Inputs are UTXO Positions to be spent, format: (blknum return nil }, } - diff --git a/cmd/plasmacli/subcmd/tx/spend.go b/cmd/plasmacli/subcmd/tx/spend.go index 71e86b3..72163bc 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 63b8101..185fa32 100644 --- a/handlers/anteHandler.go +++ b/handlers/anteHandler.go @@ -192,50 +192,26 @@ func includeDepositAnteHandler(ctx sdk.Context, ds store.DataStore, msg msgs.Inc // 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) { - // attempt to recover signers - signers := confirmSigMsg.GetSigners() - if len(signers) == 0 { - return ctx, ErrInvalidTransaction("failed recovering signers").Result(), true - } - - expectedSigners := 0 - if confirmSigMsg.Input1.Signature != [65]byte{} { - expectedSigners += 1 - } - if confirmSigMsg.Input2.Signature != [65]byte{} { - expectedSigners += 1 - } - - if len(signers) != expectedSigners { - return ctx, ErrInvalidSignature("number of signers does not equal number of signatures").Result(), true + res = validateConfirmSigMsgInput(ctx, ds, confirmSigMsg.Input1, client) + if !res.IsOK() { + return ctx, res, true } - res = validateSigMsgInput(ctx, ds, confirmSigMsg.Input1, common.BytesToAddress(signers[0]), client) + res = validateConfirmSigMsgInput(ctx, ds, confirmSigMsg.Input2, client) if !res.IsOK() { return ctx, res, true } - if len(signers) > 1 { - res = validateSigMsgInput(ctx, ds, confirmSigMsg.Input2, common.BytesToAddress(signers[1]), client) - if !res.IsOK() { - return ctx, res, true - } - } return ctx, sdk.Result{}, false } -// validates the inputs against the output store and returns the amount of the respective input -func validateSigMsgInput(ctx sdk.Context, ds store.DataStore, input plasma.Input, signer common.Address, client plasmaConn) (sdk.Result) { +// validates the inputs against the output store +func validateConfirmSigMsgInput(ctx sdk.Context, ds store.DataStore, input plasma.Input, client plasmaConn) sdk.Result { - // inputUTXO must be owned by the signer due to the prefix so we do not need to - // check the owner of the position - inputUTXO, ok := ds.GetOutput(ctx, input.Position) + _, ok := ds.GetOutput(ctx, input.Position) if !ok { return ErrInvalidInput("input, %v, does not exist", input.Position).Result() } - if !bytes.Equal(inputUTXO.Output.Owner[:], signer[:]) { - return ErrSignatureVerificationFailure(fmt.Sprintf("transaction was not signed by correct address. Got: 0x%x. Expected: 0x%x", signer, inputUTXO.Output.Owner)).Result() - } // validate inputs/confirmation signatures if not a fee utxo or deposit utxo if !input.IsDeposit() && !input.IsFee() { diff --git a/msgs/confirmSigMsg.go b/msgs/confirmSigMsg.go index 20b2ab2..671d9b2 100644 --- a/msgs/confirmSigMsg.go +++ b/msgs/confirmSigMsg.go @@ -3,8 +3,6 @@ package msgs import ( "github.com/FourthState/plasma-mvp-sidechain/plasma" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/FourthState/plasma-mvp-sidechain/utils" - "github.com/ethereum/go-ethereum/crypto" ) const ( @@ -27,27 +25,7 @@ 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 { - positionHash1 := utils.ToEthSignedMessageHash(msg.Input1.Position.Bytes()) - var addrs []sdk.AccAddress - - // recover first owner - pubKey, err := crypto.SigToPub(positionHash1, msg.Input1.Signature[:]) - if err != nil { - return nil - } - addrs = append(addrs, sdk.AccAddress(crypto.PubkeyToAddress(*pubKey).Bytes())) - - if msg.Input2.Signature != [65]byte{} { - // recover the second owner - positionHash2 := utils.ToEthSignedMessageHash(msg.Input1.Position.Bytes()) - pubKey, err = crypto.SigToPub(positionHash2, msg.Input2.Signature[:]) - if err != nil { - return nil - } - addrs = append(addrs, sdk.AccAddress(crypto.PubkeyToAddress(*pubKey).Bytes())) - } - - return addrs + return nil } // GetSignBytes returns the Keccak256 hash of the transaction. From 584152c7810061de33c67fa58493ca4958d765fa Mon Sep 17 00:00:00 2001 From: Wesley Graham Date: Mon, 16 Mar 2020 22:08:34 -0700 Subject: [PATCH 3/7] Add hardcoded signature check to antehandler --- handlers/anteHandler.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/handlers/anteHandler.go b/handlers/anteHandler.go index 185fa32..dfcb290 100644 --- a/handlers/anteHandler.go +++ b/handlers/anteHandler.go @@ -197,9 +197,11 @@ func confirmSigAnteHandler(ctx sdk.Context, ds store.DataStore, confirmSigMsg ms return ctx, res, true } - res = validateConfirmSigMsgInput(ctx, ds, confirmSigMsg.Input2, client) - if !res.IsOK() { - return ctx, res, true + if confirmSigMsg.Input2.Signature == [65]byte{1} { + res = validateConfirmSigMsgInput(ctx, ds, confirmSigMsg.Input2, client) + if !res.IsOK() { + return ctx, res, true + } } return ctx, sdk.Result{}, false @@ -208,6 +210,10 @@ func confirmSigAnteHandler(ctx sdk.Context, ds store.DataStore, confirmSigMsg ms // 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{} || 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() From 00a090cb9aade77f86c88f097a2ed2314a80b9e0 Mon Sep 17 00:00:00 2001 From: Wesley Graham Date: Mon, 16 Mar 2020 22:11:26 -0700 Subject: [PATCH 4/7] Fix typo in handler --- handlers/anteHandler.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/handlers/anteHandler.go b/handlers/anteHandler.go index dfcb290..80b6aee 100644 --- a/handlers/anteHandler.go +++ b/handlers/anteHandler.go @@ -197,11 +197,9 @@ func confirmSigAnteHandler(ctx sdk.Context, ds store.DataStore, confirmSigMsg ms return ctx, res, true } - if confirmSigMsg.Input2.Signature == [65]byte{1} { - res = validateConfirmSigMsgInput(ctx, ds, confirmSigMsg.Input2, 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 From c828a0da9275048a22b191c274293a0e22487163 Mon Sep 17 00:00:00 2001 From: Wesley Graham Date: Sun, 22 Mar 2020 20:10:14 -0700 Subject: [PATCH 5/7] Add confirmsigmsg handler --- handlers/anteHandler.go | 5 ++++- handlers/confirmSigHandler.go | 42 ++++++++++++++++++++++++++++------- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/handlers/anteHandler.go b/handlers/anteHandler.go index 80b6aee..a265794 100644 --- a/handlers/anteHandler.go +++ b/handlers/anteHandler.go @@ -208,7 +208,10 @@ func confirmSigAnteHandler(ctx sdk.Context, ds store.DataStore, confirmSigMsg ms // 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{} || input.Signature != [65]byte{1} { + if input.Signature == [65]byte{} { + return sdk.Result{} + } + if input.Signature != [65]byte{1} { return ErrInvalidSignature("invalid signature provided").Result() } diff --git a/handlers/confirmSigHandler.go b/handlers/confirmSigHandler.go index 3f62575..2be561c 100644 --- a/handlers/confirmSigHandler.go +++ b/handlers/confirmSigHandler.go @@ -1,12 +1,9 @@ package handlers import ( - "crypto/sha256" "github.com/FourthState/plasma-mvp-sidechain/msgs" - "github.com/FourthState/plasma-mvp-sidechain/plasma" "github.com/FourthState/plasma-mvp-sidechain/store" sdk "github.com/cosmos/cosmos-sdk/types" - "math/big" ) // NewSpendHandler sets the inputs of a spend msg to spent and creates new @@ -18,11 +15,40 @@ func NewConfirmSigHandler(ds store.DataStore, nextTxIndex NextTxIndex, feeUpdate panic("Msg does not implement SpendMsg") } - /* Store Transaction and create new outputs */ - // TODO: Find best way to store sigs/inputs in DS - // input1 := store.TxInput{} - // input2 := store.TxInput{} - // ds.StoreInput(ctx, input1) + /* 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++ { + 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{} } } From 19ce7cd30aefe61bb2da0d564bf80059f9e669bf Mon Sep 17 00:00:00 2001 From: Wesley Graham Date: Sun, 22 Mar 2020 20:45:28 -0700 Subject: [PATCH 6/7] Add test scaffolds --- handlers/anteHandler_test.go | 51 +++++++++++++++++++++++++++++++++++ handlers/confirmSigHandler.go | 4 +-- msgs/confirmSigMsg_test.go | 45 +++++++++++++++++++++++++++++++ msgs/txDecoder.go | 11 +++++--- 4 files changed, 105 insertions(+), 6 deletions(-) diff --git a/handlers/anteHandler_test.go b/handlers/anteHandler_test.go index a9dcc78..f90dbcd 100644 --- a/handlers/anteHandler_test.go +++ b/handlers/anteHandler_test.go @@ -519,6 +519,57 @@ 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 emptySig + msg := msgs.ConfirmSigMsg{ + Input1: plasma.NewInput(plasma.NewPosition(utils.Big1, 0, 0, nil), [65]byte{}, [][65]byte{}), + Input2:plasma.NewInput(plasma.NewPosition(utils.Big1, 0, 0, nil), [65]byte{}, [][65]byte{}), + } + + _, res, abort := handler(ctx, msg, false) + require.False(t, res.IsOK(), "Empty input signature did not error") + require.True(t, abort, "Empty input signature did not abort") + +} + +func TestAnteConfirmSigEmptyConfirmSig(t *testing.T) { + +} + +func TestAnteConfirmSigInvalidConfirmSig(t *testing.T) { + +} + +func TestAnteConfirmSigInputDNE(t *testing.T) { + +} + +func TestAnteConfirmSigInputIsDeposit(t *testing.T) { + +} + +func TestAnteConfirmSigInputIsFee(t *testing.T) { + +} + +func TestAnteConfirmSigInputNotPartOfTx(t *testing.T) { + +} + +func TestAnteConfirmSigParentExited(t *testing.T) { + +} + + + + 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 index 2be561c..923a4c3 100644 --- a/handlers/confirmSigHandler.go +++ b/handlers/confirmSigHandler.go @@ -8,7 +8,7 @@ import ( // 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, nextTxIndex NextTxIndex, feeUpdater FeeUpdater) sdk.Handler { +func NewConfirmSigHandler(ds store.DataStore) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { confirmSigMsg, ok := msg.(msgs.ConfirmSigMsg) if !ok { @@ -21,7 +21,7 @@ func NewConfirmSigHandler(ds store.DataStore, nextTxIndex NextTxIndex, feeUpdate if !ok { panic("no transaction exists for the position provided") } - + for i := 0; i < len(tx1.Transaction.Inputs); i++ { if tx1.Transaction.Inputs[i].Position.TxIndex != confirmSigMsg.Input1.Position.TxIndex { continue diff --git a/msgs/confirmSigMsg_test.go b/msgs/confirmSigMsg_test.go index c94c858..be1f4dd 100644 --- a/msgs/confirmSigMsg_test.go +++ b/msgs/confirmSigMsg_test.go @@ -1 +1,46 @@ package msgs + +import ( + "fmt" + "github.com/FourthState/plasma-mvp-sidechain/plasma" + "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{} + //{plasma.NewInput(), plasma.NewInput()}, + //{plasma.NewInput(), plasma.NewInput()}, + //{plasma.NewInput(), plasma.NewInput()}, + + 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/txDecoder.go b/msgs/txDecoder.go index 1532e1f..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 error: { %s } Decode to DepositMsg error: { %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 } From 84e5867dc2defa010058dd7349162fd88a7e27dc Mon Sep 17 00:00:00 2001 From: Wesley Graham Date: Mon, 23 Mar 2020 10:53:19 -0700 Subject: [PATCH 7/7] Add completed test cases --- app/app.go | 2 +- handlers/anteHandler_test.go | 110 ++++++++++++++++++++++++----- handlers/confirmSigHandler.go | 1 + handlers/confirmSigHandler_test.go | 62 ++++++++++++++++ msgs/confirmSigMsg_test.go | 24 +++++-- 5 files changed, 178 insertions(+), 21 deletions(-) diff --git a/app/app.go b/app/app.go index e91489a..ec274d5 100644 --- a/app/app.go +++ b/app/app.go @@ -116,7 +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, nextTxIndex, feeUpdater)) + app.Router().AddRoute(msgs.ConfirmSigMsgRoute, handlers.NewConfirmSigHandler(app.dataStore)) // custom queriers app.QueryRouter().AddRoute(store.QuerierRouteName, store.NewQuerier(app.dataStore)) diff --git a/handlers/anteHandler_test.go b/handlers/anteHandler_test.go index f90dbcd..76b9f8b 100644 --- a/handlers/anteHandler_test.go +++ b/handlers/anteHandler_test.go @@ -527,49 +527,127 @@ func TestAnteConfirmSigInvalidSig(t *testing.T) { ctx, ds := setup() handler := NewAnteHandler(ds, conn{}) - // Try to include with emptySig + // Try to include with invalid sig msg := msgs.ConfirmSigMsg{ - Input1: plasma.NewInput(plasma.NewPosition(utils.Big1, 0, 0, nil), [65]byte{}, [][65]byte{}), - Input2:plasma.NewInput(plasma.NewPosition(utils.Big1, 0, 0, nil), [65]byte{}, [][65]byte{}), + 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(), "Empty input signature did not error") - require.True(t, abort, "Empty input signature did not abort") - + require.False(t, res.IsOK(), "Invalid input signature did not error") + require.True(t, abort, "Invalid input signature did not abort") } -func TestAnteConfirmSigEmptyConfirmSig(t *testing.T) { +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 TestAnteConfirmSigInvalidConfirmSig(t *testing.T) { +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) -func TestAnteConfirmSigInputDNE(t *testing.T) { + // 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) -func TestAnteConfirmSigInputIsFee(t *testing.T) { + // 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) -func TestAnteConfirmSigParentExited(t *testing.T) { + // 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 index 923a4c3..765bfa3 100644 --- a/handlers/confirmSigHandler.go +++ b/handlers/confirmSigHandler.go @@ -23,6 +23,7 @@ func NewConfirmSigHandler(ds store.DataStore) sdk.Handler { } 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 } diff --git a/handlers/confirmSigHandler_test.go b/handlers/confirmSigHandler_test.go index 5ac8282..9c36bdc 100644 --- a/handlers/confirmSigHandler_test.go +++ b/handlers/confirmSigHandler_test.go @@ -1 +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_test.go b/msgs/confirmSigMsg_test.go index be1f4dd..889256c 100644 --- a/msgs/confirmSigMsg_test.go +++ b/msgs/confirmSigMsg_test.go @@ -3,6 +3,7 @@ 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" @@ -15,10 +16,25 @@ func TestConfirmSigMsgValidate(t *testing.T) { input2 plasma.Input } - invalidCases := []confirmSigCase{} - //{plasma.NewInput(), plasma.NewInput()}, - //{plasma.NewInput(), plasma.NewInput()}, - //{plasma.NewInput(), plasma.NewInput()}, + 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{