From 368ff820b3f0c14b562f6c41ab8e7894391850f8 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 14 Oct 2020 19:07:16 +0300 Subject: [PATCH] core: add NotValidBefore transaction attribute Close #1490 --- pkg/config/protocol_config.go | 2 ++ pkg/core/blockchain.go | 9 ++++++ pkg/core/blockchain_test.go | 38 ++++++++++++++++++++++++ pkg/core/transaction/attribute.go | 9 +++++- pkg/core/transaction/attribute_test.go | 18 +++++++++++ pkg/core/transaction/attrtype.go | 1 + pkg/core/transaction/attrtype_string.go | 4 +++ pkg/core/transaction/not_valid_before.go | 29 ++++++++++++++++++ 8 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 pkg/core/transaction/not_valid_before.go diff --git a/pkg/config/protocol_config.go b/pkg/config/protocol_config.go index 64969135d..041c68695 100644 --- a/pkg/config/protocol_config.go +++ b/pkg/config/protocol_config.go @@ -9,6 +9,8 @@ type ( ProtocolConfiguration struct { Magic netmode.Magic `yaml:"Magic"` MemPoolSize int `yaml:"MemPoolSize"` + // P2PSigExtensions enables additional signature-related transaction attributes + P2PSigExtensions bool `yaml:"P2PSigExtensions"` // SaveStorageBatch enables storage batch saving before every persist. SaveStorageBatch bool `yaml:"SaveStorageBatch"` SecondsPerBlock int `yaml:"SecondsPerBlock"` diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 34f9fb453..af03cbdc6 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1196,6 +1196,7 @@ func (bc *Blockchain) verifyHeader(currHeader, prevHeader *block.Header) error { // Various errors that could be returned upon verification. var ( + ErrTxNotYetValid = errors.New("transaction is not yet valid") ErrTxExpired = errors.New("transaction has expired") ErrInsufficientFunds = errors.New("insufficient funds") ErrTxSmallNetworkFee = errors.New("too small network fee") @@ -1294,6 +1295,14 @@ func (bc *Blockchain) verifyTxAttributes(tx *transaction.Transaction) error { if uint64(tx.NetworkFee+tx.SystemFee) < req.GasForResponse { return fmt.Errorf("%w: oracle tx has insufficient gas", ErrInvalidAttribute) } + case transaction.NotValidBeforeT: + if !bc.config.P2PSigExtensions { + return errors.New("NotValidBefore attribute was found, but P2PSigExtensions are disabled") + } + nvb := tx.Attributes[i].Value.(*transaction.NotValidBefore) + if height := bc.BlockHeight(); height < nvb.Height { + return fmt.Errorf("%w: NotValidBefore = %d, current height = %d", ErrTxNotYetValid, nvb.Height, height) + } } } return nil diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index ba7c7797e..de070334d 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -559,6 +559,44 @@ func TestVerifyTx(t *testing.T) { checkErr(t, ErrInvalidAttribute, tx) }) }) + t.Run("NotValidBefore", func(t *testing.T) { + getNVBTx := func(height uint32) *transaction.Transaction { + tx := bc.newTestTx(h, testScript) + tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.NotValidBeforeT, Value: &transaction.NotValidBefore{Height: height}}) + tx.NetworkFee += 4_000_000 // multisig check + tx.Signers = []transaction.Signer{{ + Account: testchain.CommitteeScriptHash(), + Scopes: transaction.None, + }} + rawScript := testchain.CommitteeVerificationScript() + require.NoError(t, err) + size := io.GetVarSize(tx) + netFee, sizeDelta := fee.Calculate(rawScript) + tx.NetworkFee += netFee + tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte() + data := tx.GetSignedPart() + tx.Scripts = []transaction.Witness{{ + InvocationScript: testchain.SignCommittee(data), + VerificationScript: rawScript, + }} + return tx + } + t.Run("Disabled", func(t *testing.T) { + tx := getNVBTx(bc.blockHeight + 1) + require.Error(t, bc.VerifyTx(tx)) + }) + t.Run("Enabled", func(t *testing.T) { + bc.config.P2PSigExtensions = true + t.Run("NotYetValid", func(t *testing.T) { + tx := getNVBTx(bc.blockHeight + 1) + require.True(t, errors.Is(bc.VerifyTx(tx), ErrTxNotYetValid)) + }) + t.Run("positive", func(t *testing.T) { + tx := getNVBTx(bc.blockHeight) + require.NoError(t, bc.VerifyTx(tx)) + }) + }) + }) }) } diff --git a/pkg/core/transaction/attribute.go b/pkg/core/transaction/attribute.go index 38fcf843e..26320da0c 100644 --- a/pkg/core/transaction/attribute.go +++ b/pkg/core/transaction/attribute.go @@ -35,6 +35,9 @@ func (attr *Attribute) DecodeBinary(br *io.BinReader) { case OracleResponseT: attr.Value = new(OracleResponse) attr.Value.DecodeBinary(br) + case NotValidBeforeT: + attr.Value = new(NotValidBefore) + attr.Value.DecodeBinary(br) default: br.Err = fmt.Errorf("failed decoding TX attribute usage: 0x%2x", int(attr.Type)) return @@ -46,7 +49,7 @@ func (attr *Attribute) EncodeBinary(bw *io.BinWriter) { bw.WriteB(byte(attr.Type)) switch attr.Type { case HighPriority: - case OracleResponseT: + case OracleResponseT, NotValidBeforeT: attr.Value.EncodeBinary(bw) default: bw.Err = fmt.Errorf("failed encoding TX attribute usage: 0x%2x", attr.Type) @@ -78,6 +81,10 @@ func (attr *Attribute) UnmarshalJSON(data []byte) error { // value, we can unmarshal the same data. The overhead is minimal. attr.Value = new(OracleResponse) return json.Unmarshal(data, attr.Value) + case "NotValidBefore": + attr.Type = NotValidBeforeT + attr.Value = new(NotValidBefore) + return json.Unmarshal(data, attr.Value) default: return errors.New("wrong Type") diff --git a/pkg/core/transaction/attribute_test.go b/pkg/core/transaction/attribute_test.go index 9072373ce..4de8c3667 100644 --- a/pkg/core/transaction/attribute_test.go +++ b/pkg/core/transaction/attribute_test.go @@ -27,6 +27,15 @@ func TestAttribute_EncodeBinary(t *testing.T) { } testserdes.EncodeDecodeBinary(t, attr, new(Attribute)) }) + t.Run("NotValidBefore", func(t *testing.T) { + attr := &Attribute{ + Type: NotValidBeforeT, + Value: &NotValidBefore{ + Height: 123, + }, + } + testserdes.EncodeDecodeBinary(t, attr, new(Attribute)) + }) } func TestAttribute_MarshalJSON(t *testing.T) { @@ -63,4 +72,13 @@ func TestAttribute_MarshalJSON(t *testing.T) { require.Equal(t, attr, actual) testserdes.EncodeDecodeBinary(t, attr, new(Attribute)) }) + t.Run("NotValidBefore", func(t *testing.T) { + attr := &Attribute{ + Type: NotValidBeforeT, + Value: &NotValidBefore{ + Height: 123, + }, + } + testserdes.MarshalUnmarshalJSON(t, attr, new(Attribute)) + }) } diff --git a/pkg/core/transaction/attrtype.go b/pkg/core/transaction/attrtype.go index 4f687e088..6b1367c70 100644 --- a/pkg/core/transaction/attrtype.go +++ b/pkg/core/transaction/attrtype.go @@ -9,6 +9,7 @@ type AttrType uint8 const ( HighPriority AttrType = 1 OracleResponseT AttrType = 0x11 // OracleResponse + NotValidBeforeT AttrType = 0xe0 // NotValidBefore ) func (a AttrType) allowMultiple() bool { diff --git a/pkg/core/transaction/attrtype_string.go b/pkg/core/transaction/attrtype_string.go index f520f46a1..e45529acb 100644 --- a/pkg/core/transaction/attrtype_string.go +++ b/pkg/core/transaction/attrtype_string.go @@ -10,11 +10,13 @@ func _() { var x [1]struct{} _ = x[HighPriority-1] _ = x[OracleResponseT-17] + _ = x[NotValidBeforeT-224] } const ( _AttrType_name_0 = "HighPriority" _AttrType_name_1 = "OracleResponse" + _AttrType_name_2 = "NotValidBefore" ) func (i AttrType) String() string { @@ -23,6 +25,8 @@ func (i AttrType) String() string { return _AttrType_name_0 case i == 17: return _AttrType_name_1 + case i == 224: + return _AttrType_name_2 default: return "AttrType(" + strconv.FormatInt(int64(i), 10) + ")" } diff --git a/pkg/core/transaction/not_valid_before.go b/pkg/core/transaction/not_valid_before.go new file mode 100644 index 000000000..c1596a277 --- /dev/null +++ b/pkg/core/transaction/not_valid_before.go @@ -0,0 +1,29 @@ +package transaction + +import ( + "encoding/binary" + + "github.com/nspcc-dev/neo-go/pkg/io" +) + +// NotValidBefore represents attribute with the height transaction is not valid before. +type NotValidBefore struct { + Height uint32 `json:"height"` +} + +// DecodeBinary implements io.Serializable interface. +func (n *NotValidBefore) DecodeBinary(br *io.BinReader) { + bytes := br.ReadVarBytes(4) + n.Height = binary.LittleEndian.Uint32(bytes) +} + +// EncodeBinary implements io.Serializable interface. +func (n *NotValidBefore) EncodeBinary(w *io.BinWriter) { + bytes := make([]byte, 4) + binary.LittleEndian.PutUint32(bytes, n.Height) + w.WriteVarBytes(bytes) +} + +func (n *NotValidBefore) toJSONMap(m map[string]interface{}) { + m["height"] = n.Height +}