Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add broadcast confirm sig logic #188

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
106 changes: 106 additions & 0 deletions cmd/plasmacli/subcmd/tx/broadcast.go
Original file line number Diff line number Diff line change
@@ -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{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although the anteHandler for this msg will fail if the sig is wrong, we should do client side validation here before sending off the message.

Use: "broadcast-sigs <input1, input2>",
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 <input1>
plasmacli broadcast-sigs <input1, input2>
`,
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
},
}
1 change: 1 addition & 0 deletions cmd/plasmacli/subcmd/tx/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func RootCmd() *cobra.Command {
IncludeCmd(),
SpendCmd(),
SignCmd(),
BroadcastSigsCmd(),
)

return txCmd
Expand Down
1 change: 0 additions & 1 deletion cmd/plasmacli/subcmd/tx/spend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
64 changes: 62 additions & 2 deletions handlers/anteHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
Expand Down Expand Up @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this if we're already doing GetTxWithPoisition?

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() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the input is a deposit or fee, we should not return sdk.Result{}. It should be an error. There are no confirm signature for these kinds of inputs. Otherwise this will get sent to the handler

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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we make this message better w.r.t confirm signatures? like 'cannot include confirm signatures of a input transaction that has exited..."

}
}
}

return sdk.Result{}
}
129 changes: 129 additions & 0 deletions handlers/anteHandler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
Loading