Skip to content

Commit

Permalink
Merge pull request #3845 from oasisprotocol/tjanez/testvector-valid
Browse files Browse the repository at this point in the history
go/consensus/api/transaction/testvector: Extend TestVector to suport the notion of a (statically) valid transaction
  • Loading branch information
tjanez committed Apr 10, 2021
2 parents 210751e + 3313495 commit 541bad8
Show file tree
Hide file tree
Showing 25 changed files with 431 additions and 124 deletions.
1 change: 1 addition & 0 deletions .changelog/3845.breaking.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
go/common/version: Move `ConsensusProtocol` ver to top of `ProtocolVersions`
5 changes: 5 additions & 0 deletions .changelog/3845.breaking.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
go/upgrade: Limit `Descriptor`'s field values

Add variables defining `Descriptor`'s field value limits:
`MinDescriptorVersion`, `MaxDescriptorVersion`, `MinUpgradeHandlerLength`,
`MaxUpgradeHandlerLength`, `MinUpgradeEpoch`, `MaxUpgradeEpoch`.
3 changes: 3 additions & 0 deletions .changelog/3845.breaking.3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
go/common/entity: Rename `LatestEntityDescriptorVersion`

Rename it to `LatestDescriptorVersion`.
1 change: 1 addition & 0 deletions .changelog/3845.feature.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
go/common/version: Add `ValidateBasic()` to `Version` and `ProtocolVersions`
1 change: 1 addition & 0 deletions .changelog/3845.feature.3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
go/common/entity: Add `{Min,Max}DescriptorVersion` constants
14 changes: 14 additions & 0 deletions .changelog/3845.internal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
go/consensus/api/transaction/testvectors: Extend meaning of the `Valid` field

Extend meaning of `TestVector`'s `Valid` field to also indicate that the
given test vector's transaction passes basic static validation (besides having
a valid signature and being correctly serialized).

Update `MakeTestVector()` and `MakeTestVectorWithSigner()` functions to take
the `valid` argument indicating whether the transaction is valid or not.

Update go/registry/gen_vectors, go/staking/gen_vectors and
go/governance/gen_vectors packages.

Expand go/governance/gen_vectors package to cover more (invalid) transaction
cases.
14 changes: 12 additions & 2 deletions docs/consensus/test-vectors.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ of test vectors. They can be generated for the following consensus services:

* [Staking]
* [Registry]
* [Governance]

[Staking]: staking.md#test-vectors
[Registry]: registry.md#test-vectors
[Governance]: governance.md#test-vectors

## Structure

Expand Down Expand Up @@ -37,8 +39,16 @@ objects (test vectors). Each test vector has the following fields:
CBOR encoding is a binary encoding it also needs to be Base64-encoded) signed
transaction. **This is what is actually broadcast to the network.**

* `valid` is a boolean flag indicating whether the given test vector represents
a valid transaction.
* `valid` is a boolean flag indicating indicating whether the given test vector
represents a valid transaction, including:

* transaction having a valid signature,
* transaction being correctly serialized,
* transaction passing basic static validation.

_NOTE: Even if a transaction passes basic static validation, it may still
**not** be a valid transaction on the given network due to invalid nonce, or
due to some specific parameters set on the network._

* `signer_private_key` is the Ed25519 private key that was used to sign the
transaction in the test vector.
Expand Down
34 changes: 18 additions & 16 deletions go/common/entity/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@ var (
)

const (
// LatestEntityDescriptorVersion is the latest entity descriptor version that should be used for
// all new descriptors. Using earlier versions may be rejected.
LatestEntityDescriptorVersion = 2

// Minimum and maximum descriptor versions that are allowed.
minEntityDescriptorVersion = 1
maxEntityDescriptorVersion = LatestEntityDescriptorVersion
// LatestDescriptorVersion is the latest descriptor version that should be
// used for all new descriptors. Using earlier versions may be rejected.
LatestDescriptorVersion = 2

// MinDescriptorVersion is the minimum descriptor version that is allowed.
MinDescriptorVersion = 1
// MaxDescriptorVersion is the maximum descriptor version that is allowed.
MaxDescriptorVersion = LatestDescriptorVersion
)

// Entity represents an entity that controls one or more Nodes and or
Expand Down Expand Up @@ -99,18 +100,19 @@ func (e *Entity) ValidateBasic(strictVersion bool) error {
switch strictVersion {
case true:
// Only the latest version is allowed.
if v != LatestEntityDescriptorVersion {
return fmt.Errorf("invalid entity descriptor version (expected: %d got: %d)",
LatestEntityDescriptorVersion,
if v != LatestDescriptorVersion {
return fmt.Errorf("invalid entity descriptor version: %d (expected: %d)",
v,
LatestDescriptorVersion,
)
}
case false:
// A range of versions is allowed.
if v < minEntityDescriptorVersion || v > maxEntityDescriptorVersion {
return fmt.Errorf("invalid entity descriptor version (min: %d max: %d)",
minEntityDescriptorVersion,
maxEntityDescriptorVersion,
if v < MinDescriptorVersion || v > MaxDescriptorVersion {
return fmt.Errorf("invalid entity descriptor version: %d (min: %d max: %d)",
v,
MinDescriptorVersion,
MaxDescriptorVersion,
)
}
}
Expand Down Expand Up @@ -178,7 +180,7 @@ func LoadDescriptor(f string) (*Entity, error) {
func GenerateWithSigner(baseDir string, signer signature.Signer, template *Entity) (*Entity, error) {
// Generate a new entity.
ent := &Entity{
Versioned: cbor.NewVersioned(LatestEntityDescriptorVersion),
Versioned: cbor.NewVersioned(LatestDescriptorVersion),
ID: signer.Public(),
}
if template != nil {
Expand Down Expand Up @@ -256,6 +258,6 @@ func SignEntity(signer signature.Signer, context signature.Context, entity *Enti
func init() {
testEntitySigner = memorySigner.NewTestSigner("ekiden test entity key seed")

testEntity.Versioned = cbor.NewVersioned(LatestEntityDescriptorVersion)
testEntity.Versioned = cbor.NewVersioned(LatestDescriptorVersion)
testEntity.ID = testEntitySigner.Public()
}
53 changes: 38 additions & 15 deletions go/common/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ type Version struct {
Patch uint16 `json:"patch,omitempty"`
}

// ValidateBasic does basic validation of a protocol version.
func (v Version) ValidateBasic() error {
empty := Version{}
if v == empty {
return fmt.Errorf("invalid version: %s", empty)
}
return nil
}

// ToU64 returns the version as platform-dependent uint64.
func (v Version) ToU64() uint64 {
return (uint64(v.Major) << 32) | (uint64(v.Minor) << 16) | (uint64(v.Patch))
Expand Down Expand Up @@ -72,16 +81,6 @@ var (
// This is mostly used for reporting and metrics.
GitBranch = ""

// RuntimeHostProtocol versions the protocol between the Oasis node(s) and
// the runtime.
//
// NOTE: This version must be synced with runtime/src/common/version.rs.
RuntimeHostProtocol = Version{Major: 2, Minor: 0, Patch: 0}

// RuntimeCommitteeProtocol versions the P2P protocol used by the runtime
// committee members.
RuntimeCommitteeProtocol = Version{Major: 2, Minor: 0, Patch: 0}

// ConsensusProtocol versions all data structures and processing used by
// the epochtime, beacon, registry, roothash, etc. modules that are
// backend by consensus.
Expand All @@ -92,6 +91,16 @@ var (
// via Tendermint's version checks.
ConsensusProtocol = Version{Major: 4, Minor: 0, Patch: 0}

// RuntimeHostProtocol versions the protocol between the Oasis node(s) and
// the runtime.
//
// NOTE: This version must be synced with runtime/src/common/version.rs.
RuntimeHostProtocol = Version{Major: 2, Minor: 0, Patch: 0}

// RuntimeCommitteeProtocol versions the P2P protocol used by the runtime
// committee members.
RuntimeCommitteeProtocol = Version{Major: 2, Minor: 0, Patch: 0}

// TendermintAppVersion is Tendermint ABCI application's version computed by
// masking non-major consensus protocol version segments to 0 to be
// compatible with Tendermint's version checks.
Expand All @@ -107,20 +116,34 @@ var (

// ProtocolVersions are the protocol versions.
type ProtocolVersions struct {
ConsensusProtocol Version `json:"consensus_protocol"`
RuntimeHostProtocol Version `json:"runtime_host_protocol"`
RuntimeCommitteeProtocol Version `json:"runtime_committee_protocol"`
ConsensusProtocol Version `json:"consensus_protocol"`
}

// ValidateBasic does basic validation checks of the protocol versions.
func (pv *ProtocolVersions) ValidateBasic() error {
if err := pv.ConsensusProtocol.ValidateBasic(); err != nil {
return fmt.Errorf("invalid Consensus protocol version: %w", err)
}
if err := pv.RuntimeHostProtocol.ValidateBasic(); err != nil {
return fmt.Errorf("invalid Runtime Host protocol version: %w", err)
}
if err := pv.RuntimeCommitteeProtocol.ValidateBasic(); err != nil {
return fmt.Errorf("invalid Runtime Committee protocol version: %w", err)
}
return nil
}

// Compatible returns if the two protocol versions are compatible.
func (pv *ProtocolVersions) Compatible(other ProtocolVersions) bool {
if pv.RuntimeHostProtocol.MaskNonMajor() != other.RuntimeHostProtocol.MaskNonMajor() {
if pv.ConsensusProtocol.MaskNonMajor() != other.ConsensusProtocol.MaskNonMajor() {
return false
}
if pv.RuntimeCommitteeProtocol.MaskNonMajor() != other.RuntimeCommitteeProtocol.MaskNonMajor() {
if pv.RuntimeHostProtocol.MaskNonMajor() != other.RuntimeHostProtocol.MaskNonMajor() {
return false
}
if pv.ConsensusProtocol.MaskNonMajor() != other.ConsensusProtocol.MaskNonMajor() {
if pv.RuntimeCommitteeProtocol.MaskNonMajor() != other.RuntimeCommitteeProtocol.MaskNonMajor() {
return false
}
return true
Expand Down Expand Up @@ -150,9 +173,9 @@ func (pv ProtocolVersions) PrettyType() (interface{}, error) {

// Versions are current protocol versions.
var Versions = ProtocolVersions{
ConsensusProtocol,
RuntimeHostProtocol,
RuntimeCommitteeProtocol,
ConsensusProtocol,
}

func parseSemVerStr(s string) Version {
Expand Down
99 changes: 99 additions & 0 deletions go/common/version/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,49 @@ import (
"github.com/stretchr/testify/require"
)

func TestValidateBasic(t *testing.T) {
require := require.New(t)

for _, tc := range []struct {
msg string
v Version
shouldErr bool
}{
{
msg: "empty version should fail",
v: Version{},
shouldErr: true,
},
{
msg: "valid version should not fail",
v: Version{1, 2, 3},
shouldErr: false,
},
{
msg: "version with only major version should not fail",
v: Version{Major: 1},
shouldErr: false,
},
{
msg: "version with only minor version should not fail",
v: Version{Minor: 2},
shouldErr: false,
},
{
msg: "version with only patch version should not fail",
v: Version{Patch: 13},
shouldErr: false,
},
} {
err := tc.v.ValidateBasic()
if tc.shouldErr {
require.NotNil(err, tc.msg)
continue
}
require.Nil(err, tc.msg)
}
}

func TestMaskNonMajor(t *testing.T) {
require := require.New(t)

Expand Down Expand Up @@ -91,6 +134,62 @@ func TestConvertGoModulesVersion(t *testing.T) {
}
}

func TestProtocolVersionsValidateBasic(t *testing.T) {
require := require.New(t)

for _, tc := range []struct {
msg string
v ProtocolVersions
shouldErr bool
}{
{
msg: "empty protocol versions should fail",
v: ProtocolVersions{},
shouldErr: true,
},
{
msg: "empty protocol versions should fail",
v: ProtocolVersions{
ConsensusProtocol: Version{},
RuntimeHostProtocol: Version{},
RuntimeCommitteeProtocol: Version{},
},
shouldErr: true,
},
{
msg: "protocol versions with only consensus version should fail",
v: ProtocolVersions{
ConsensusProtocol: Version{1, 2, 3},
},
shouldErr: true,
},
{
msg: "protocol versions with only runtime versions should fail",
v: ProtocolVersions{
RuntimeHostProtocol: Version{2, 0, 1},
RuntimeCommitteeProtocol: Version{3, 2, 1},
},
shouldErr: true,
},
{
msg: "valid protocol versions should not fail",
v: ProtocolVersions{
ConsensusProtocol: Version{1, 0, 1},
RuntimeHostProtocol: Version{2, 0, 0},
RuntimeCommitteeProtocol: Version{4, 2, 3},
},
shouldErr: false,
},
} {
err := tc.v.ValidateBasic()
if tc.shouldErr {
require.NotNil(err, tc.msg)
continue
}
require.Nil(err, tc.msg)
}
}

func TestProtocolVersionCompatible(t *testing.T) {
for _, v := range []struct {
versions func() ProtocolVersions
Expand Down
24 changes: 14 additions & 10 deletions go/consensus/api/transaction/testvectors/testvectors.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,31 @@ import (

const keySeedPrefix = "oasis-core test vectors: "

// TestVector is a staking message test vector.
// TestVector is an Oasis transaction test vector.
type TestVector struct {
Kind string `json:"kind"`
SignatureContext string `json:"signature_context"`
Tx interface{} `json:"tx"`
SignedTx transaction.SignedTransaction `json:"signed_tx"`
EncodedTx []byte `json:"encoded_tx"`
EncodedSignedTx []byte `json:"encoded_signed_tx"`
Valid bool `json:"valid"`
SignerPrivateKey []byte `json:"signer_private_key"`
SignerPublicKey signature.PublicKey `json:"signer_public_key"`
// Valid indicates whether the transaction is (statically) valid.
// NOTE: This means that the transaction passes basic static validation, but
// it may still not be valid on the given network due to invalid nonce,
// or due to some specific parameters set on the network.
Valid bool `json:"valid"`
SignerPrivateKey []byte `json:"signer_private_key"`
SignerPublicKey signature.PublicKey `json:"signer_public_key"`
}

// MakeTestVector generates a new test vector from a transction.
func MakeTestVector(kind string, tx *transaction.Transaction) TestVector {
// MakeTestVector generates a new test vector from a transaction.
func MakeTestVector(kind string, tx *transaction.Transaction, valid bool) TestVector {
signer := memorySigner.NewTestSigner(keySeedPrefix + kind)
return MakeTestVectorWithSigner(kind, tx, signer)
return MakeTestVectorWithSigner(kind, tx, valid, signer)
}

// MakeTestVectorWithSigner generates a new test vector from a transction using a specific signer.
func MakeTestVectorWithSigner(kind string, tx *transaction.Transaction, signer signature.Signer) TestVector {
// MakeTestVectorWithSigner generates a new test vector from a transaction using a specific signer.
func MakeTestVectorWithSigner(kind string, tx *transaction.Transaction, valid bool, signer signature.Signer) TestVector {
sigTx, err := transaction.Sign(signer, tx)
if err != nil {
panic(err)
Expand Down Expand Up @@ -60,7 +64,7 @@ func MakeTestVectorWithSigner(kind string, tx *transaction.Transaction, signer s
SignedTx: *sigTx,
EncodedTx: cbor.Marshal(tx),
EncodedSignedTx: cbor.Marshal(sigTx),
Valid: true,
Valid: valid,
SignerPrivateKey: signer.(signature.UnsafeSigner).UnsafeBytes(),
SignerPublicKey: signer.Public(),
}
Expand Down
2 changes: 1 addition & 1 deletion go/consensus/tendermint/apps/governance/governance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func initValidatorsEscrowState(
signers = append(signers, entitySigner)

ent := entity.Entity{
Versioned: cbor.NewVersioned(entity.LatestEntityDescriptorVersion),
Versioned: cbor.NewVersioned(entity.LatestDescriptorVersion),
ID: entitySigner.Public(),
Nodes: []signature.PublicKey{nodeSigner.Public()},
}
Expand Down
Loading

0 comments on commit 541bad8

Please sign in to comment.