diff --git a/.changelog/3845.breaking.1.md b/.changelog/3845.breaking.1.md new file mode 100644 index 00000000000..c9c82337971 --- /dev/null +++ b/.changelog/3845.breaking.1.md @@ -0,0 +1 @@ +go/common/version: Move `ConsensusProtocol` ver to top of `ProtocolVersions` diff --git a/.changelog/3845.breaking.2.md b/.changelog/3845.breaking.2.md new file mode 100644 index 00000000000..3fd1375ac34 --- /dev/null +++ b/.changelog/3845.breaking.2.md @@ -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`. diff --git a/.changelog/3845.breaking.3.md b/.changelog/3845.breaking.3.md new file mode 100644 index 00000000000..2878861e6e3 --- /dev/null +++ b/.changelog/3845.breaking.3.md @@ -0,0 +1,3 @@ +go/common/entity: Rename `LatestEntityDescriptorVersion` + +Rename it to `LatestDescriptorVersion`. diff --git a/.changelog/3845.feature.2.md b/.changelog/3845.feature.2.md new file mode 100644 index 00000000000..bde113b5ccb --- /dev/null +++ b/.changelog/3845.feature.2.md @@ -0,0 +1 @@ +go/common/version: Add `ValidateBasic()` to `Version` and `ProtocolVersions` diff --git a/.changelog/3845.feature.3.md b/.changelog/3845.feature.3.md new file mode 100644 index 00000000000..05140aada79 --- /dev/null +++ b/.changelog/3845.feature.3.md @@ -0,0 +1 @@ +go/common/entity: Add `{Min,Max}DescriptorVersion` constants diff --git a/.changelog/3845.internal.md b/.changelog/3845.internal.md new file mode 100644 index 00000000000..bbeed590aa6 --- /dev/null +++ b/.changelog/3845.internal.md @@ -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. diff --git a/docs/consensus/test-vectors.md b/docs/consensus/test-vectors.md index 4f8a41154bc..35cfcb32d55 100644 --- a/docs/consensus/test-vectors.md +++ b/docs/consensus/test-vectors.md @@ -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 @@ -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. diff --git a/go/common/entity/entity.go b/go/common/entity/entity.go index 85c980c5383..76657afbc0f 100644 --- a/go/common/entity/entity.go +++ b/go/common/entity/entity.go @@ -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 @@ -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, ) } } @@ -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 { @@ -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() } diff --git a/go/common/version/version.go b/go/common/version/version.go index f6d09d9ba99..c6211ff9628 100644 --- a/go/common/version/version.go +++ b/go/common/version/version.go @@ -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)) @@ -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. @@ -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. @@ -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 @@ -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 { diff --git a/go/common/version/version_test.go b/go/common/version/version_test.go index 6cf1f9aab3f..e864d1a62d0 100644 --- a/go/common/version/version_test.go +++ b/go/common/version/version_test.go @@ -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) @@ -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 diff --git a/go/consensus/api/transaction/testvectors/testvectors.go b/go/consensus/api/transaction/testvectors/testvectors.go index 0ec95704809..7f499071bc6 100644 --- a/go/consensus/api/transaction/testvectors/testvectors.go +++ b/go/consensus/api/transaction/testvectors/testvectors.go @@ -11,7 +11,7 @@ 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"` @@ -19,19 +19,23 @@ type TestVector struct { 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) @@ -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(), } diff --git a/go/consensus/tendermint/apps/governance/governance_test.go b/go/consensus/tendermint/apps/governance/governance_test.go index 4e7879ac03f..fd2bed3729f 100644 --- a/go/consensus/tendermint/apps/governance/governance_test.go +++ b/go/consensus/tendermint/apps/governance/governance_test.go @@ -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()}, } diff --git a/go/consensus/tendermint/apps/registry/transactions_test.go b/go/consensus/tendermint/apps/registry/transactions_test.go index 6b430062520..40130a8fa68 100644 --- a/go/consensus/tendermint/apps/registry/transactions_test.go +++ b/go/consensus/tendermint/apps/registry/transactions_test.go @@ -407,7 +407,7 @@ func TestRegisterNode(t *testing.T) { // Prepare a test entity that owns the nodes. ent := entity.Entity{ - Versioned: cbor.NewVersioned(entity.LatestEntityDescriptorVersion), + Versioned: cbor.NewVersioned(entity.LatestDescriptorVersion), ID: tcd.entitySigner.Public(), Nodes: []signature.PublicKey{tcd.nodeSigner.Public()}, } diff --git a/go/genesis/genesis_test.go b/go/genesis/genesis_test.go index 56c90db57f3..aef96c92cc5 100644 --- a/go/genesis/genesis_test.go +++ b/go/genesis/genesis_test.go @@ -172,7 +172,7 @@ func TestGenesisSanityCheck(t *testing.T) { // Note that this test entity has no nodes by design, those will be added // later by various tests. testEntity := &entity.Entity{ - Versioned: cbor.NewVersioned(entity.LatestEntityDescriptorVersion), + Versioned: cbor.NewVersioned(entity.LatestDescriptorVersion), ID: validPK, } signedTestEntity := signEntityOrDie(signer, testEntity) diff --git a/go/governance/gen_vectors/main.go b/go/governance/gen_vectors/main.go index 1ae0d7a3400..217d66adac3 100644 --- a/go/governance/gen_vectors/main.go +++ b/go/governance/gen_vectors/main.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "math" + "os" beacon "github.com/oasisprotocol/oasis-core/go/beacon/api" "github.com/oasisprotocol/oasis-core/go/common/cbor" @@ -15,9 +16,30 @@ import ( "github.com/oasisprotocol/oasis-core/go/consensus/api/transaction" "github.com/oasisprotocol/oasis-core/go/consensus/api/transaction/testvectors" governance "github.com/oasisprotocol/oasis-core/go/governance/api" - "github.com/oasisprotocol/oasis-core/go/upgrade/api" + upgrade "github.com/oasisprotocol/oasis-core/go/upgrade/api" ) +func valideSubmitProposal(v uint16, epoch uint64, handler string, target version.ProtocolVersions) bool { + if v < upgrade.MinDescriptorVersion || v > upgrade.MaxDescriptorVersion || + epoch < uint64(upgrade.MinUpgradeEpoch) || epoch > uint64(upgrade.MaxUpgradeEpoch) || + len(handler) < upgrade.MinUpgradeHandlerLength || len(handler) > upgrade.MaxUpgradeHandlerLength { + return false + } + if err := target.ValidateBasic(); err != nil { + return false + } + return true +} + +func valideCastVote(vote governance.Vote) bool { + for _, v := range []governance.Vote{governance.VoteAbstain, governance.VoteYes, governance.VoteNo} { + if vote == v { + return true + } + } + return false +} + func main() { // Configure chain context for all signatures using chain domain separation. var chainContext hash.Hash @@ -35,11 +57,12 @@ func main() { } { // Generate different nonces. for _, nonce := range []uint64{0, 1, 10, 42, 1000, 1_000_000, 10_000_000, math.MaxUint64} { - // Valid submit upgrade proposal transaction. - for _, v := range []uint16{0, api.LatestDescriptorVersion} { - for _, epoch := range []uint64{0, 1000, 10_000_000} { - for _, handler := range []string{"", "descriptor-handler"} { - for _, version := range []version.ProtocolVersions{ + + // Generate upgrade proposal transactions. + for _, v := range []uint16{0, upgrade.LatestDescriptorVersion} { + for _, epoch := range []uint64{0, 1000, 10_000_000, math.MaxUint64 - 1, math.MaxUint64} { + for _, handler := range []string{"", "descriptor-handler", "tooooooo-long-33-char-description"} { + for _, target := range []version.ProtocolVersions{ {}, {ConsensusProtocol: version.Version{Major: 1, Minor: 2, Patch: 3}}, { @@ -64,23 +87,24 @@ func main() { for _, tx := range []*transaction.Transaction{ governance.NewSubmitProposalTx(nonce, fee, &governance.ProposalContent{ Upgrade: &governance.UpgradeProposal{ - Descriptor: api.Descriptor{ + Descriptor: upgrade.Descriptor{ Versioned: cbor.NewVersioned(v), Handler: handler, - Target: version, + Target: target, Epoch: beacon.EpochTime(epoch), }, }, }), } { - vectors = append(vectors, testvectors.MakeTestVector("SubmitProposal", tx)) + valid := valideSubmitProposal(v, epoch, handler, target) + vectors = append(vectors, testvectors.MakeTestVector("SubmitProposal", tx, valid)) } } } } } - // Valid submit cancel upgrade proposal transaction. + // Generate cancel upgrade proposal transactions. for _, id := range []uint64{0, 1000, 10_000_000, math.MaxUint64} { for _, tx := range []*transaction.Transaction{ governance.NewSubmitProposalTx(nonce, fee, &governance.ProposalContent{ @@ -89,20 +113,23 @@ func main() { }, }), } { - vectors = append(vectors, testvectors.MakeTestVector("SubmitProposal", tx)) + vectors = append(vectors, testvectors.MakeTestVector("SubmitProposal", tx, true)) } } - // Valid cast vote transactions. + // Generate cast vote transactions. for _, id := range []uint64{0, 1000, 10_000_000, math.MaxUint64} { - for _, vote := range []governance.Vote{governance.VoteAbstain, governance.VoteYes, governance.VoteNo} { + for _, vote := range []governance.Vote{ + governance.VoteAbstain, governance.VoteYes, governance.VoteNo, + } { for _, tx := range []*transaction.Transaction{ governance.NewCastVoteTx(nonce, fee, &governance.ProposalVote{ ID: id, Vote: vote, }), } { - vectors = append(vectors, testvectors.MakeTestVector("SubmitProposal", tx)) + valid := valideCastVote(vote) + vectors = append(vectors, testvectors.MakeTestVector("SubmitProposal", tx, valid)) } } } @@ -110,6 +137,9 @@ func main() { } // Generate output. - jsonOut, _ := json.MarshalIndent(&vectors, "", " ") + jsonOut, err := json.MarshalIndent(&vectors, "", " ") + if err != nil { + fmt.Fprintf(os.Stderr, "Error encoding test vectors: %v\n", err) + } fmt.Printf("%s", jsonOut) } diff --git a/go/oasis-node/cmd/debug/txsource/workload/governance.go b/go/oasis-node/cmd/debug/txsource/workload/governance.go index 70410920ec7..ffc003487ac 100644 --- a/go/oasis-node/cmd/debug/txsource/workload/governance.go +++ b/go/oasis-node/cmd/debug/txsource/workload/governance.go @@ -143,7 +143,7 @@ func (g *governanceWorkload) doUpgradeProposal() error { maxUpgradeEpoch := minUpgradeEpoch + int64(3*g.parameters.UpgradeMinEpochDiff) // [minUpgradeEpoch, maxUpgradeEpoch] upgradeEpoch := beacon.EpochTime(g.rng.Int63n(maxUpgradeEpoch-minUpgradeEpoch+1) + minUpgradeEpoch) - nameSuffix := make([]byte, 20) + nameSuffix := make([]byte, 8) if _, err := g.rng.Read(nameSuffix); err != nil { return err } diff --git a/go/oasis-node/cmd/debug/txsource/workload/registration.go b/go/oasis-node/cmd/debug/txsource/workload/registration.go index 8277ca29be1..b218d2728c9 100644 --- a/go/oasis-node/cmd/debug/txsource/workload/registration.go +++ b/go/oasis-node/cmd/debug/txsource/workload/registration.go @@ -235,7 +235,7 @@ func (r *registration) Run( // nolint: gocyclo } ent := &entity.Entity{ - Versioned: cbor.NewVersioned(entity.LatestEntityDescriptorVersion), + Versioned: cbor.NewVersioned(entity.LatestDescriptorVersion), ID: entityAccs[i].signer.Public(), } diff --git a/go/oasis-node/cmd/registry/entity/entity.go b/go/oasis-node/cmd/registry/entity/entity.go index 7db7fd37d82..ffae8280a48 100644 --- a/go/oasis-node/cmd/registry/entity/entity.go +++ b/go/oasis-node/cmd/registry/entity/entity.go @@ -369,7 +369,7 @@ func loadOrGenerateEntity(dataDir string, generate bool) (*entity.Entity, signat if generate { template := &entity.Entity{ - Versioned: cbor.NewVersioned(entity.LatestEntityDescriptorVersion), + Versioned: cbor.NewVersioned(entity.LatestDescriptorVersion), } if viper.GetBool(CfgReuseSigner) { diff --git a/go/oasis-test-runner/scenario/e2e/runtime/governance_upgrade.go b/go/oasis-test-runner/scenario/e2e/runtime/governance_upgrade.go index bb7fc4403a0..366fa95c380 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/governance_upgrade.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/governance_upgrade.go @@ -356,7 +356,19 @@ func (sc *governanceConsensusUpgradeImpl) Run(childEnv *env.Env) error { // noli case true: target = version.Versions default: - target = version.ProtocolVersions{ConsensusProtocol: version.FromU64(192)} + target = version.ProtocolVersions{ + ConsensusProtocol: version.FromU64(192), + RuntimeHostProtocol: version.Version{ + Major: 1, + Minor: 2, + Patch: 3, + }, + RuntimeCommitteeProtocol: version.Version{ + Major: 4, + Minor: 5, + Patch: 6, + }, + } } content := &api.ProposalContent{ diff --git a/go/registry/gen_vectors/main.go b/go/registry/gen_vectors/main.go index d30f4b08bcd..51524ca6ca0 100644 --- a/go/registry/gen_vectors/main.go +++ b/go/registry/gen_vectors/main.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "math" + "os" "github.com/oasisprotocol/oasis-core/go/common/cbor" "github.com/oasisprotocol/oasis-core/go/common/crypto/hash" @@ -17,6 +18,13 @@ import ( registry "github.com/oasisprotocol/oasis-core/go/registry/api" ) +func valideRegisterEntity(v uint16) bool { + if v < entity.MinDescriptorVersion || v > entity.MaxDescriptorVersion { + return false + } + return true +} + func main() { // Configure chain context for all signatures using chain domain separation. var chainContext hash.Hash @@ -34,35 +42,42 @@ func main() { } { // Generate different nonces. for _, nonce := range []uint64{0, 1, 10, 42, 1000, 1_000_000, 10_000_000, math.MaxUint64} { - // Valid register entity transactions. - entitySigner := memorySigner.NewTestSigner("oasis-core registry test vectors: RegisterEntity signer") - for _, numNodes := range []int{0, 1, 2, 5} { - ent := entity.Entity{ - Versioned: cbor.NewVersioned(entity.LatestEntityDescriptorVersion), - ID: entitySigner.Public(), - } - for i := 0; i < numNodes; i++ { - nodeSigner := memorySigner.NewTestSigner(fmt.Sprintf("oasis core registry test vectors: node signer %d", i)) - ent.Nodes = append(ent.Nodes, nodeSigner.Public()) - } - sigEnt, err := entity.SignEntity(entitySigner, registry.RegisterEntitySignatureContext, &ent) - if err != nil { - panic(err) + + // Generate register entity transactions. + for _, v := range []uint16{1, entity.LatestDescriptorVersion} { + for _, numNodes := range []int{0, 1, 2, 5} { + entitySigner := memorySigner.NewTestSigner("oasis-core registry test vectors: RegisterEntity signer") + ent := entity.Entity{ + Versioned: cbor.NewVersioned(v), + ID: entitySigner.Public(), + } + for i := 0; i < numNodes; i++ { + nodeSigner := memorySigner.NewTestSigner(fmt.Sprintf("oasis core registry test vectors: node signer %d", i)) + ent.Nodes = append(ent.Nodes, nodeSigner.Public()) + } + sigEnt, err := entity.SignEntity(entitySigner, registry.RegisterEntitySignatureContext, &ent) + if err != nil { + panic(err) + } + tx := registry.NewRegisterEntityTx(nonce, fee, sigEnt) + valid := valideRegisterEntity(v) + vectors = append(vectors, testvectors.MakeTestVectorWithSigner("RegisterEntity", tx, valid, entitySigner)) } - tx := registry.NewRegisterEntityTx(nonce, fee, sigEnt) - vectors = append(vectors, testvectors.MakeTestVectorWithSigner("RegisterEntity", tx, entitySigner)) } - // Valid unfreeze node transactions. + // Generate unfreeze node transactions. nodeSigner := memorySigner.NewTestSigner("oasis-core registry test vectors: UnfreezeNode signer") tx := registry.NewUnfreezeNodeTx(nonce, fee, ®istry.UnfreezeNode{ NodeID: nodeSigner.Public(), }) - vectors = append(vectors, testvectors.MakeTestVector("UnfreezeNode", tx)) + vectors = append(vectors, testvectors.MakeTestVector("UnfreezeNode", tx, true)) } } // Generate output. - jsonOut, _ := json.MarshalIndent(&vectors, "", " ") + jsonOut, err := json.MarshalIndent(&vectors, "", " ") + if err != nil { + fmt.Fprintf(os.Stderr, "Error encoding test vectors: %v\n", err) + } fmt.Printf("%s", jsonOut) } diff --git a/go/registry/tests/tester.go b/go/registry/tests/tester.go index 744c0c7f51b..54e6a3410d2 100644 --- a/go/registry/tests/tester.go +++ b/go/registry/tests/tester.go @@ -1446,7 +1446,7 @@ func NewTestEntities(seed []byte, n int) ([]*TestEntity, error) { return nil, err } ent.Entity = &entity.Entity{ - Versioned: cbor.NewVersioned(entity.LatestEntityDescriptorVersion), + Versioned: cbor.NewVersioned(entity.LatestDescriptorVersion), ID: ent.Signer.Public(), } diff --git a/go/staking/gen_vectors/main.go b/go/staking/gen_vectors/main.go index 1763229f47c..feff14ada2d 100644 --- a/go/staking/gen_vectors/main.go +++ b/go/staking/gen_vectors/main.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "math" + "os" beacon "github.com/oasisprotocol/oasis-core/go/beacon/api" "github.com/oasisprotocol/oasis-core/go/common/crypto/hash" @@ -33,7 +34,8 @@ func main() { } { // Generate different nonces. for _, nonce := range []uint64{0, 1, 10, 42, 1000, 1_000_000, 10_000_000, math.MaxUint64} { - // Valid transfer transactions. + + // Generate transfer transactions. transferDst := memorySigner.NewTestSigner("oasis-core staking test vectors: Transfer dst") transferDstAddr := staking.NewAddress(transferDst.Public()) for _, amt := range []uint64{0, 1000, 10_000_000} { @@ -43,22 +45,22 @@ func main() { Amount: *quantity.NewFromUint64(amt), }), } { - vectors = append(vectors, testvectors.MakeTestVector("Transfer", tx)) + vectors = append(vectors, testvectors.MakeTestVector("Transfer", tx, true)) } } - // Valid burn transactions. + // Generate burn transactions. for _, amt := range []uint64{0, 1000, 10_000_000} { for _, tx := range []*transaction.Transaction{ staking.NewBurnTx(nonce, fee, &staking.Burn{ Amount: *quantity.NewFromUint64(amt), }), } { - vectors = append(vectors, testvectors.MakeTestVector("Burn", tx)) + vectors = append(vectors, testvectors.MakeTestVector("Burn", tx, true)) } } - // Valid escrow transactions. + // Generate escrow transactions. escrowDst := memorySigner.NewTestSigner("oasis-core staking test vectors: Escrow dst") escrowDstAddr := staking.NewAddress(escrowDst.Public()) for _, amt := range []uint64{0, 1000, 10_000_000} { @@ -68,11 +70,11 @@ func main() { Amount: *quantity.NewFromUint64(amt), }), } { - vectors = append(vectors, testvectors.MakeTestVector("Escrow", tx)) + vectors = append(vectors, testvectors.MakeTestVector("Escrow", tx, true)) } } - // Valid reclaim escrow transactions. + // Generate reclaim escrow transactions. escrowSrc := memorySigner.NewTestSigner("oasis-core staking test vectors: ReclaimEscrow src") escrowSrcAddr := staking.NewAddress(escrowSrc.Public()) for _, amt := range []uint64{0, 1000, 10_000_000} { @@ -82,11 +84,11 @@ func main() { Shares: *quantity.NewFromUint64(amt), }), } { - vectors = append(vectors, testvectors.MakeTestVector("ReclaimEscrow", tx)) + vectors = append(vectors, testvectors.MakeTestVector("ReclaimEscrow", tx, true)) } } - // Valid amend commission schedule transactions. + // Generate amend commission schedule transactions. for _, steps := range []int{0, 1, 2, 5} { for _, startEpoch := range []uint64{0, 10, 1000, 1_000_000} { for _, rate := range []uint64{0, 10, 1000, 10_000, 50_000, 100_000} { @@ -106,12 +108,12 @@ func main() { tx := staking.NewAmendCommissionScheduleTx(nonce, fee, &staking.AmendCommissionSchedule{ Amendment: cs, }) - vectors = append(vectors, testvectors.MakeTestVector("AmendCommissionSchedule", tx)) + vectors = append(vectors, testvectors.MakeTestVector("AmendCommissionSchedule", tx, true)) } } } - // Valid allow transactions. + // Generate allow transactions. beneficiary := memorySigner.NewTestSigner("oasis-core staking test vectors: Allow beneficiary") beneficiaryAddr := staking.NewAddress(beneficiary.Public()) for _, amt := range []uint64{0, 1000, 10_000_000} { @@ -123,12 +125,12 @@ func main() { AmountChange: *quantity.NewFromUint64(amt), }), } { - vectors = append(vectors, testvectors.MakeTestVector("Withdraw", tx)) + vectors = append(vectors, testvectors.MakeTestVector("Withdraw", tx, true)) } } } - // Valid withdraw transactions. + // Generate withdraw transactions. withdrawSrc := memorySigner.NewTestSigner("oasis-core staking test vectors: Withdraw src") withdrawSrcAddr := staking.NewAddress(withdrawSrc.Public()) for _, amt := range []uint64{0, 1000, 10_000_000} { @@ -138,13 +140,16 @@ func main() { Amount: *quantity.NewFromUint64(amt), }), } { - vectors = append(vectors, testvectors.MakeTestVector("Withdraw", tx)) + vectors = append(vectors, testvectors.MakeTestVector("Withdraw", tx, true)) } } } } // Generate output. - jsonOut, _ := json.MarshalIndent(&vectors, "", " ") + jsonOut, err := json.MarshalIndent(&vectors, "", " ") + if err != nil { + fmt.Fprintf(os.Stderr, "Error encoding test vectors: %v\n", err) + } fmt.Printf("%s", jsonOut) } diff --git a/go/upgrade/api/api.go b/go/upgrade/api/api.go index 72f4a5349bc..8d287ceca7e 100644 --- a/go/upgrade/api/api.go +++ b/go/upgrade/api/api.go @@ -47,12 +47,23 @@ const ( // descriptors. LatestDescriptorVersion = 1 - // Minimum and maximum descriptor versions that are allowed. - minDescriptorVersion = 1 - maxDescriptorVersion = LatestDescriptorVersion + // MinDescriptorVersion is the minimum descriptor version that is allowed. + MinDescriptorVersion = 1 + // MaxDescriptorVersion is the maximum descriptor version that is allowed. + MaxDescriptorVersion = LatestDescriptorVersion // LatestPendingUpgradeVersion is the latest pending upgrade struct version. LatestPendingUpgradeVersion = 1 + + // MinUpgradeHandlerLength is the minimum length of upgrade handler's name. + MinUpgradeHandlerLength = 3 + // MaxUpgradeHandlerLength is the maximum length of upgrade handler's name. + MaxUpgradeHandlerLength = 32 + + // MinUpgradeEpoch is the minimum upgrade epoch. + MinUpgradeEpoch = beacon.EpochTime(1) + // MaxUpgradeEpoch is the maximum upgrade epoch. + MaxUpgradeEpoch = beacon.EpochInvalid - 1 ) var ( @@ -100,21 +111,29 @@ func (d *Descriptor) Equals(other *Descriptor) bool { // ValidateBasic does basic validation checks of the upgrade descriptor. func (d Descriptor) ValidateBasic() error { - if d.V < minDescriptorVersion || d.V > maxDescriptorVersion { - return fmt.Errorf("invalid upgrade descriptor version (min: %d max: %d)", - minDescriptorVersion, - maxDescriptorVersion, + if d.V < MinDescriptorVersion || d.V > MaxDescriptorVersion { + return fmt.Errorf("invalid upgrade descriptor version: %d (min: %d max: %d)", + d.V, + MinDescriptorVersion, + MaxDescriptorVersion, ) } - if d.Handler == "" { - return fmt.Errorf("empty descriptor handler") + if len(d.Handler) < MinUpgradeHandlerLength || len(d.Handler) > MaxUpgradeHandlerLength { + return fmt.Errorf("invalid upgrade descriptor handler length: %d (min: %d max: %d)", + len(d.Handler), + MinUpgradeHandlerLength, + MaxUpgradeHandlerLength, + ) } - empty := version.ProtocolVersions{} - if d.Target == empty { - return fmt.Errorf("empty target version") + if err := d.Target.ValidateBasic(); err != nil { + return fmt.Errorf("invalid upgrade descriptor target version: %w", err) } - if d.Epoch < 1 { - return fmt.Errorf("invalid descriptor epoch: %d", d.Epoch) + if d.Epoch < MinUpgradeEpoch || d.Epoch > MaxUpgradeEpoch { + return fmt.Errorf("invalid upgrade descriptor epoch: %d (min: %d max: %d)", + d.Epoch, + MinUpgradeEpoch, + MaxUpgradeEpoch, + ) } return nil diff --git a/go/upgrade/api/api_test.go b/go/upgrade/api/api_test.go index 83cdf9f37ce..ef0218b28b7 100644 --- a/go/upgrade/api/api_test.go +++ b/go/upgrade/api/api_test.go @@ -21,7 +21,27 @@ func TestValidateBasic(t *testing.T) { shouldErr: true, }, { - msg: "invalid epoch should fail", + msg: "descriptor version below min should fail", + d: &Descriptor{ + Versioned: cbor.NewVersioned(MinDescriptorVersion - 1), + Handler: "TestHandler", + Target: version.Versions, + Epoch: 42, + }, + shouldErr: true, + }, + { + msg: "descriptor version above max should fail", + d: &Descriptor{ + Versioned: cbor.NewVersioned(MaxDescriptorVersion + 1), + Handler: "TestHandler", + Target: version.Versions, + Epoch: 42, + }, + shouldErr: true, + }, + { + msg: "epoch below min epoch should fail", d: &Descriptor{ Versioned: cbor.NewVersioned(LatestDescriptorVersion), Handler: "TestHandler", @@ -31,35 +51,78 @@ func TestValidateBasic(t *testing.T) { shouldErr: true, }, { - msg: "invalid target should fail", + msg: "epoch above max epoch should fail", d: &Descriptor{ Versioned: cbor.NewVersioned(LatestDescriptorVersion), Handler: "TestHandler", - Target: version.ProtocolVersions{}, + Target: version.Versions, + Epoch: MaxUpgradeEpoch + 1, + }, + shouldErr: true, + }, + { + msg: "too short handler should fail", + d: &Descriptor{ + Versioned: cbor.NewVersioned(LatestDescriptorVersion), + Handler: "TH", + Target: version.Versions, Epoch: 42, }, shouldErr: true, }, { - msg: "invalid descriptor version should fail", + msg: "too long handler should fail", d: &Descriptor{ - Versioned: cbor.NewVersioned(maxDescriptorVersion + 1), - Handler: "TestHandler", + Versioned: cbor.NewVersioned(LatestDescriptorVersion), + Handler: "Tooooooo-Long-33-Char-TestHandler", Target: version.Versions, Epoch: 42, }, shouldErr: true, }, { - msg: "invalid descriptor version should fail", + msg: "empty target version should fail", d: &Descriptor{ - Versioned: cbor.NewVersioned(minDescriptorVersion - 1), + Versioned: cbor.NewVersioned(LatestDescriptorVersion), Handler: "TestHandler", - Target: version.Versions, + Target: version.ProtocolVersions{}, Epoch: 42, }, shouldErr: true, }, + { + msg: "only consensus version in target version should fail", + d: &Descriptor{ + Versioned: cbor.NewVersioned(LatestDescriptorVersion), + Handler: "TestHandler", + Target: version.ProtocolVersions{ + ConsensusProtocol: version.Version{Major: 1, Minor: 2, Patch: 3}, + }, + Epoch: 42, + }, + shouldErr: true, + }, + { + msg: "empty runtime host protocol target subversion should fail", + d: &Descriptor{ + Versioned: cbor.NewVersioned(LatestDescriptorVersion), + Handler: "TestHandler", + Target: version.ProtocolVersions{ + ConsensusProtocol: version.Version{ + Major: 0, + Minor: 12, + Patch: 1, + }, + RuntimeCommitteeProtocol: version.Version{ + Major: 42, + Minor: 0, + Patch: 1, + }, + }, + Epoch: 42, + }, + shouldErr: true, + }, { msg: "valid descriptor should not fail", d: &Descriptor{ diff --git a/go/upgrade/migrations/dummy.go b/go/upgrade/migrations/dummy.go index db7f0a5e576..4aad4a2718b 100644 --- a/go/upgrade/migrations/dummy.go +++ b/go/upgrade/migrations/dummy.go @@ -32,7 +32,7 @@ var ( func init() { entitySigner = memory.NewTestSigner(testSigningSeed) - TestEntity.Versioned = cbor.NewVersioned(entity.LatestEntityDescriptorVersion) + TestEntity.Versioned = cbor.NewVersioned(entity.LatestDescriptorVersion) TestEntity.ID = entitySigner.Public() }