From 161fb4f6668cb705e3e2203b6162ccd21f7df095 Mon Sep 17 00:00:00 2001 From: wz Date: Fri, 17 May 2019 10:57:28 +0800 Subject: [PATCH] V0.1 votetx input (#67) * add unvote struct * modify ComputeOutputID * add uint test * fix --- protocol/bc/types/map.go | 22 ++++++++++++++ protocol/bc/types/txinput.go | 62 +++++++++++++++++++++++++++++++++++--- protocol/bc/types/txinput_test.go | 57 +++++++++++++++++++++++++++++++++++ protocol/bc/types/txoutput.go | 12 ++++++-- protocol/bc/types/txoutput_test.go | 32 +++++++++++++++++++- protocol/bc/types/unvote.go | 38 +++++++++++++++++++++++ 6 files changed, 216 insertions(+), 7 deletions(-) create mode 100644 protocol/bc/types/unvote.go diff --git a/protocol/bc/types/map.go b/protocol/bc/types/map.go index b4a4ff07..348c203d 100644 --- a/protocol/bc/types/map.go +++ b/protocol/bc/types/map.go @@ -98,6 +98,28 @@ func mapTx(tx *TxData) (headerID bc.Hash, hdr *bc.TxHeader, entryMap map[bc.Hash Ref: &coinbaseID, Value: &value, } + + case *UnvoteInput: + // create entry for prevout + prog := &bc.Program{VmVersion: inp.VMVersion, Code: inp.ControlProgram} + src := &bc.ValueSource{ + Ref: &inp.SourceID, + Value: &inp.AssetAmount, + Position: inp.SourcePosition, + } + prevout := bc.NewVoteOutput(src, prog, 0, inp.Vote) // ordinal doesn't matter for prevouts, only for result outputs + prevoutID := addEntry(prevout) + // create entry for spend + spend := bc.NewSpend(&prevoutID, uint64(i)) + spend.WitnessArguments = inp.Arguments + spendID := addEntry(spend) + // setup mux + muxSources[i] = &bc.ValueSource{ + Ref: &spendID, + Value: &inp.AssetAmount, + } + spends = append(spends, spend) + } } diff --git a/protocol/bc/types/txinput.go b/protocol/bc/types/txinput.go index c9b4e8e6..2da01345 100644 --- a/protocol/bc/types/txinput.go +++ b/protocol/bc/types/txinput.go @@ -14,6 +14,7 @@ const ( IssuanceInputType uint8 = iota SpendInputType CoinbaseInputType + UnvoteInputType ) type ( @@ -38,6 +39,9 @@ func (t *TxInput) AssetAmount() bc.AssetAmount { switch inp := t.TypedInput.(type) { case *SpendInput: return inp.AssetAmount + + case *UnvoteInput: + return inp.AssetAmount } return bc.AssetAmount{} } @@ -48,6 +52,8 @@ func (t *TxInput) AssetID() bc.AssetID { case *SpendInput: return *inp.AssetId + case *UnvoteInput: + return *inp.AssetId } return bc.AssetID{} } @@ -57,15 +63,23 @@ func (t *TxInput) Amount() uint64 { switch inp := t.TypedInput.(type) { case *SpendInput: return inp.Amount + + case *UnvoteInput: + return inp.Amount } return 0 } // ControlProgram return the control program of the spend input func (t *TxInput) ControlProgram() []byte { - if si, ok := t.TypedInput.(*SpendInput); ok { - return si.ControlProgram + switch inp := t.TypedInput.(type) { + case *SpendInput: + return inp.ControlProgram + + case *UnvoteInput: + return inp.ControlProgram } + return nil } @@ -74,6 +88,9 @@ func (t *TxInput) Arguments() [][]byte { switch inp := t.TypedInput.(type) { case *SpendInput: return inp.Arguments + + case *UnvoteInput: + return inp.Arguments } return nil } @@ -83,14 +100,22 @@ func (t *TxInput) SetArguments(args [][]byte) { switch inp := t.TypedInput.(type) { case *SpendInput: inp.Arguments = args + + case *UnvoteInput: + inp.Arguments = args } } // SpentOutputID calculate the hash of spended output func (t *TxInput) SpentOutputID() (o bc.Hash, err error) { - if si, ok := t.TypedInput.(*SpendInput); ok { - o, err = ComputeOutputID(&si.SpendCommitment) + switch inp := t.TypedInput.(type) { + case *SpendInput: + o, err = ComputeOutputID(&inp.SpendCommitment, SpendInputType, nil) + + case *UnvoteInput: + o, err = ComputeOutputID(&inp.SpendCommitment, UnvoteInputType, inp.Vote) } + return o, err } @@ -122,6 +147,13 @@ func (t *TxInput) readFrom(r *blockchain.Reader) (err error) { return err } + case UnvoteInputType: + ui := new(UnvoteInput) + t.TypedInput = ui + if ui.UnvoteCommitmentSuffix, err = ui.SpendCommitment.readFrom(r, 1); err != nil { + return err + } + default: return fmt.Errorf("unsupported input type %d", icType[0]) } @@ -141,6 +173,15 @@ func (t *TxInput) readFrom(r *blockchain.Reader) (err error) { if inp.Arguments, err = blockchain.ReadVarstrList(r); err != nil { return err } + + case *UnvoteInput: + if inp.Arguments, err = blockchain.ReadVarstrList(r); err != nil { + return err + } + if inp.Vote, err = blockchain.ReadVarstr31(r); err != nil { + return err + } + } return nil }) @@ -180,6 +221,12 @@ func (t *TxInput) writeInputCommitment(w io.Writer) (err error) { if _, err = blockchain.WriteVarstr31(w, inp.Arbitrary); err != nil { return errors.Wrap(err, "writing coinbase arbitrary") } + + case *UnvoteInput: + if _, err = w.Write([]byte{UnvoteInputType}); err != nil { + return err + } + return inp.SpendCommitment.writeExtensibleString(w, inp.UnvoteCommitmentSuffix, t.AssetVersion) } return nil } @@ -193,6 +240,13 @@ func (t *TxInput) writeInputWitness(w io.Writer) error { case *SpendInput: _, err := blockchain.WriteVarstrList(w, inp.Arguments) return err + + case *UnvoteInput: + if _, err := blockchain.WriteVarstrList(w, inp.Arguments); err != nil { + return err + } + _, err := blockchain.WriteVarstr31(w, inp.Vote) + return err } return nil } diff --git a/protocol/bc/types/txinput_test.go b/protocol/bc/types/txinput_test.go index 110b8b38..ec78bbef 100644 --- a/protocol/bc/types/txinput_test.go +++ b/protocol/bc/types/txinput_test.go @@ -66,6 +66,63 @@ func TestSerializationSpend(t *testing.T) { } } +func TestSerializationUnvote(t *testing.T) { + arguments := [][]byte{ + []byte("arguments1"), + []byte("arguments2"), + } + + unvote := NewUnvoteInput(arguments, testutil.MustDecodeHash("fad5195a0c8e3b590b86a3c0a95e7529565888508aecca96e9aeda633002f409"), testutil.MustDecodeAsset("fe9791d71b67ee62515e08723c061b5ccb952a80d804417c8aeedf7f633c524a"), 254354, 3, []byte("spendProgram"), []byte("af594006a40837d9f028daabb6d589df0b9138daefad5683e5233c2646279217294a8d532e60863bcf196625a35fb8ceeffa3c09610eb92dcfb655a947f13269")) + + wantHex := strings.Join([]string{ + "01", // asset version + "54", // input commitment length + "03", // unvote type flag + "52", // unvote commitment length + "fad5195a0c8e3b590b86a3c0a95e7529565888508aecca96e9aeda633002f409", // source id + "fe9791d71b67ee62515e08723c061b5ccb952a80d804417c8aeedf7f633c524a", // assetID + "92c30f", // amount + "03", // source position + "01", // vm version + "0c", // unvote program length + "7370656e6450726f6772616d", // unvote program + "9901", // witness length + "02", // argument array length + "0a", // first argument length + "617267756d656e747331", // first argument data + "0a", // second argument length + "617267756d656e747332", // second argument data + "8001", //xpub length + "6166353934303036613430383337643966303238646161626236643538396466306239313338646165666164353638336535323333633236343632373932313732393461386435333265363038363362636631393636323561333566623863656566666133633039363130656239326463666236353561393437663133323639", //voter xpub + }, "") + + // Test convert struct to hex + var buffer bytes.Buffer + if err := unvote.writeTo(&buffer); err != nil { + t.Fatal(err) + } + + gotHex := hex.EncodeToString(buffer.Bytes()) + if gotHex != wantHex { + t.Errorf("serialization bytes = %s want %s", gotHex, wantHex) + } + + // Test convert hex to struct + var gotUnvote TxInput + decodeHex, err := hex.DecodeString(wantHex) + if err != nil { + t.Fatal(err) + } + + if err := gotUnvote.readFrom(blockchain.NewReader(decodeHex)); err != nil { + t.Fatal(err) + } + + if !testutil.DeepEqual(*unvote, gotUnvote) { + t.Errorf("expected marshaled/unmarshaled txinput to be:\n%sgot:\n%s", spew.Sdump(*unvote), spew.Sdump(gotUnvote)) + } +} + func TestSerializationCoinbase(t *testing.T) { coinbase := NewCoinbaseInput([]byte("arbitrary")) wantHex := strings.Join([]string{ diff --git a/protocol/bc/types/txoutput.go b/protocol/bc/types/txoutput.go index 7cc2b618..aca99209 100644 --- a/protocol/bc/types/txoutput.go +++ b/protocol/bc/types/txoutput.go @@ -206,7 +206,7 @@ func (to *TxOutput) writeOutputCommitment(w io.Writer) error { // ComputeOutputID assembles an intra-chain(!) output entry given a spend // commitment and computes and returns its corresponding entry ID. -func ComputeOutputID(sc *SpendCommitment) (h bc.Hash, err error) { +func ComputeOutputID(sc *SpendCommitment, inputType uint8, vote []byte) (h bc.Hash, err error) { defer func() { if r, ok := recover().(error); ok { err = r @@ -217,7 +217,15 @@ func ComputeOutputID(sc *SpendCommitment) (h bc.Hash, err error) { Value: &sc.AssetAmount, Position: sc.SourcePosition, } - o := bc.NewIntraChainOutput(src, &bc.Program{VmVersion: sc.VMVersion, Code: sc.ControlProgram}, 0) + var o bc.Entry + switch inputType { + case SpendInputType: + o = bc.NewIntraChainOutput(src, &bc.Program{VmVersion: sc.VMVersion, Code: sc.ControlProgram}, 0) + case UnvoteInputType: + o = bc.NewVoteOutput(src, &bc.Program{VmVersion: sc.VMVersion, Code: sc.ControlProgram}, 0, vote) + default: + return h, fmt.Errorf("Input type error:[%v]", inputType) + } h = bc.EntryID(o) return h, nil diff --git a/protocol/bc/types/txoutput_test.go b/protocol/bc/types/txoutput_test.go index b978073a..a6c4eba0 100644 --- a/protocol/bc/types/txoutput_test.go +++ b/protocol/bc/types/txoutput_test.go @@ -106,6 +106,8 @@ func TestComputeOutputID(t *testing.T) { btmAssetID := testutil.MustDecodeAsset("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") cases := []struct { sc *SpendCommitment + inputType uint8 + vote []byte wantOutputID string }{ { @@ -116,6 +118,8 @@ func TestComputeOutputID(t *testing.T) { VMVersion: 1, ControlProgram: testutil.MustDecodeHexString("0014cb9f2391bafe2bc1159b2c4c8a0f17ba1b4dd94e"), }, + inputType: SpendInputType, + vote: nil, wantOutputID: "73eea4d38b22ffd60fc30d0941f3875f45e29d424227bfde100193a08568605b", }, { @@ -126,12 +130,38 @@ func TestComputeOutputID(t *testing.T) { VMVersion: 1, ControlProgram: testutil.MustDecodeHexString("001418549d84daf53344d32563830c7cf979dc19d5c0"), }, + inputType: SpendInputType, + vote: nil, wantOutputID: "8371e76fd1c873503a326268bfd286ffe13009a0f1140d2c858e8187825696ab", }, + { + sc: &SpendCommitment{ + AssetAmount: bc.AssetAmount{AssetId: &btmAssetID, Amount: 999}, + SourceID: testutil.MustDecodeHash("993d3797fa3b2d958f300e599987dc10904b13f56ce89d158f60f9131424e0e2"), + SourcePosition: 2, + VMVersion: 1, + ControlProgram: testutil.MustDecodeHexString("00145c47f3a0dd3e1e9956fe5b0f897072ed33f9efb9"), + }, + inputType: UnvoteInputType, + vote: []byte("af594006a40837d9f028daabb6d589df0b9138daefad5683e5233c2646279217294a8d532e60863bcf196625a35fb8ceeffa3c09610eb92dcfb655a947f13269"), + wantOutputID: "c95701822db14f5c647158762e4d4b9bff270bfd3040f0ca32cb87c18e377429", + }, + { + sc: &SpendCommitment{ + AssetAmount: bc.AssetAmount{AssetId: &btmAssetID, Amount: 999}, + SourceID: testutil.MustDecodeHash("993d3797fa3b2d958f300e599987dc10904b13f56ce89d158f60f9131424e0e2"), + SourcePosition: 2, + VMVersion: 1, + ControlProgram: testutil.MustDecodeHexString("00145c47f3a0dd3e1e9956fe5b0f897072ed33f9efb9"), + }, + inputType: UnvoteInputType, + vote: []byte(""), + wantOutputID: "8f17b871f3fd07bfe778e92c272e26d4bb19258c90af08310ef32feb526eaf9c", + }, } for _, c := range cases { - outputID, err := ComputeOutputID(c.sc) + outputID, err := ComputeOutputID(c.sc, c.inputType, c.vote) if err != nil { t.Fatal(err) } diff --git a/protocol/bc/types/unvote.go b/protocol/bc/types/unvote.go new file mode 100644 index 00000000..810a8f39 --- /dev/null +++ b/protocol/bc/types/unvote.go @@ -0,0 +1,38 @@ +package types + +import ( + "github.com/vapor/protocol/bc" +) + +// UnvoteInput satisfies the TypedInput interface and represents a unvote transaction. +type UnvoteInput struct { + UnvoteCommitmentSuffix []byte // The unconsumed suffix of the output commitment + Arguments [][]byte // Witness + Vote []byte // voter xpub + SpendCommitment +} + +// NewUnvoteInput create a new UnvoteInput struct. +func NewUnvoteInput(arguments [][]byte, sourceID bc.Hash, assetID bc.AssetID, amount, sourcePos uint64, controlProgram []byte, vote []byte) *TxInput { + sc := SpendCommitment{ + AssetAmount: bc.AssetAmount{ + AssetId: &assetID, + Amount: amount, + }, + SourceID: sourceID, + SourcePosition: sourcePos, + VMVersion: 1, + ControlProgram: controlProgram, + } + return &TxInput{ + AssetVersion: 1, + TypedInput: &UnvoteInput{ + SpendCommitment: sc, + Arguments: arguments, + Vote: vote, + }, + } +} + +// InputType is the interface function for return the input type. +func (ui *UnvoteInput) InputType() uint8 { return SpendInputType } -- 2.11.0