Skip to content

Commit

Permalink
core: add NotValidBefore transaction attribute
Browse files Browse the repository at this point in the history
Close #1490
  • Loading branch information
AnnaShaleva committed Oct 20, 2020
1 parent e9a8e95 commit aea44e4
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 4 deletions.
2 changes: 2 additions & 0 deletions pkg/config/protocol_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
9 changes: 9 additions & 0 deletions pkg/core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down
38 changes: 38 additions & 0 deletions pkg/core/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
})
})
})
})
}

Expand Down
9 changes: 8 additions & 1 deletion pkg/core/transaction/attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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")

Expand Down
18 changes: 18 additions & 0 deletions pkg/core/transaction/attribute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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))
})
}
1 change: 1 addition & 0 deletions pkg/core/transaction/attrtype.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type AttrType uint8
const (
HighPriority AttrType = 1
OracleResponseT AttrType = 0x11 // OracleResponse
NotValidBeforeT AttrType = 0x12 // NotValidBefore
)

func (a AttrType) allowMultiple() bool {
Expand Down
12 changes: 9 additions & 3 deletions pkg/core/transaction/attrtype_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions pkg/core/transaction/not_valid_before.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package transaction

import "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) {
n.Height = br.ReadU32LE()
}

// EncodeBinary implements io.Serializable interface.
func (n *NotValidBefore) EncodeBinary(w *io.BinWriter) {
w.WriteU32LE(n.Height)
}

func (n *NotValidBefore) toJSONMap(m map[string]interface{}) {
m["height"] = n.Height
}

0 comments on commit aea44e4

Please sign in to comment.