Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

go/registry: Add transaction test vector generator #3059

Merged
merged 3 commits into from
Jun 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changelog/3059.internal.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
go/registry: Add transaction test vector generator
1 change: 1 addition & 0 deletions .changelog/3059.internal.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
go/common/prettyprint: Add PrettyType for use in test vectors
1 change: 1 addition & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ ignore:
- "go/oasis-test-runner" # E2E test runner.
- "go/oasis-net-runner" # Test local network runner.
- "go/staking/gen_vectors" # Staking test vector generator.
- "go/registry/gen_vectors" # Registry test vector generator.
- "go/storage/fuzz" # Fuzz tests.
- "go/storage/mkvs/fuzz" # Fuzz tests.
- "go/consensus/tendermint/fuzz" # Fuzz tests.
9 changes: 9 additions & 0 deletions docs/consensus/registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ the future to enable runtimes to be self-governing.

## Methods

The following sections describe the methods supported by the consensus registry
service. To generate test vectors for various registry [transactions], run:

```bash
make -C go registry/gen_vectors
```

[transactions]: transactions.md

### Register Entity

Entity registration enables a new entity to be created. A new register entity
Expand Down
19 changes: 9 additions & 10 deletions docs/consensus/staking.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,6 @@ the [consensus service API documentation].
[consensus service API documentation]: https://pkg.go.dev/github.com/oasisprotocol/oasis-core/go/staking/api?tab=doc
<!-- markdownlint-enable line-length -->

## Test Vectors

To generate test vectors for various staking [transactions], run:

```bash
make -C go test-vectors/staking
```

[transactions]: transactions.md

## Accounts

A staking account is an entry in the staking ledger. It can hold both general
Expand Down Expand Up @@ -205,6 +195,15 @@ be specified a number of epochs in the future, controlled by the

## Methods

The following sections describe the methods supported by the consensus staking
service. To generate test vectors for various staking [transactions], run:

```bash
make -C go staking/gen_vectors
```

[transactions]: transactions.md

### Transfer

Transfer enables token transfer between different accounts in the staking
Expand Down
13 changes: 6 additions & 7 deletions go/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,12 @@ mkvs:
build-helpers: $(test-helpers)

# List of test vectors to generate.
test-vectors := staking
test-vectors-targets := staking/gen_vectors \
registry/gen_vectors

$(test-vectors):
@$(ECHO) "$(MAGENTA)*** Generating test vectors for $@...$(OFF)"
@$(GO) run ./$@/gen_vectors

gen-test-vectors: $(test-vectors)
$(test-vectors-targets):
@$(ECHO) "$(MAGENTA)*** Generating test vectors ($@)...$(OFF)"
@$(GO) run ./$@

# Format code.
fmt:
Expand Down Expand Up @@ -135,7 +134,7 @@ clean:
.PHONY: \
generate $(go-binaries) build \
$(test-helpers) build-helpers \
$(test-vectors) gen-test-vectors \
$(test-vectors-targets) \
fmt lint \
$(test-targets) test force-test \
$(fuzz-targets) build-fuzz \
Expand Down
32 changes: 28 additions & 4 deletions go/common/crypto/signature/signature.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,13 +453,25 @@ func (p PrettySigned) PrettyPrint(prefix string, w io.Writer) {
fmt.Fprintf(w, "%s%s\n", prefix, data)
}

// PrettyType returns a representation of the type that can be used for pretty printing.
func (p PrettySigned) PrettyType() (interface{}, error) {
return p, nil
}

// NewPrettySigned creates a new PrettySigned instance that can be
// used for pretty printing signed values.
func NewPrettySigned(s Signed, b interface{}) *PrettySigned {
func NewPrettySigned(s Signed, b interface{}) (*PrettySigned, error) {
if pp, ok := b.(prettyprint.PrettyPrinter); ok {
var err error
if b, err = pp.PrettyType(); err != nil {
return nil, fmt.Errorf("failed to pretty print body: %w", err)
}
}

return &PrettySigned{
Body: b,
Signature: s.Signature,
}
}, nil
}

// MultiSigned is a blob signed by multiple public keys.
Expand Down Expand Up @@ -557,13 +569,25 @@ func (p PrettyMultiSigned) PrettyPrint(prefix string, w io.Writer) {
fmt.Fprintf(w, "%s%s\n", prefix, data)
}

// PrettyType returns a representation of the type that can be used for pretty printing.
func (p PrettyMultiSigned) PrettyType() (interface{}, error) {
return p, nil
}

// NewPrettyMultiSigned creates a new PrettySigned instance that can be
// used for pretty printing multi-signed values.
func NewPrettyMultiSigned(s MultiSigned, b interface{}) *PrettyMultiSigned {
func NewPrettyMultiSigned(s MultiSigned, b interface{}) (*PrettyMultiSigned, error) {
if pp, ok := b.(prettyprint.PrettyPrinter); ok {
var err error
if b, err = pp.PrettyType(); err != nil {
return nil, fmt.Errorf("failed to pretty print body: %w", err)
}
}

return &PrettyMultiSigned{
Body: b,
Signatures: s.Signatures,
}
}, nil
}

// SignedPublicKey is a signed blob containing a PublicKey.
Expand Down
18 changes: 13 additions & 5 deletions go/common/entity/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,14 +189,22 @@ func (s *SignedEntity) Open(context signature.Context, entity *Entity) error { /
// PrettyPrint writes a pretty-printed representation of the type
// to the given writer.
func (s SignedEntity) PrettyPrint(prefix string, w io.Writer) {
var e Entity
if err := cbor.Unmarshal(s.Signed.Blob, &e); err != nil {
fmt.Fprintf(w, "%s<malformed: %s>\n", prefix, err)
pt, err := s.PrettyType()
if err != nil {
fmt.Fprintf(w, "%s<error: %s>\n", prefix, err)
return
}

pp := signature.NewPrettySigned(s.Signed, e)
pp.PrettyPrint(prefix, w)
pt.(prettyprint.PrettyPrinter).PrettyPrint(prefix, w)
}

// PrettyType returns a representation of the type that can be used for pretty printing.
func (s SignedEntity) PrettyType() (interface{}, error) {
var e Entity
if err := cbor.Unmarshal(s.Signed.Blob, &e); err != nil {
return nil, fmt.Errorf("malformed signed blob: %w", err)
}
return signature.NewPrettySigned(s.Signed, e)
}

// SignEntity serializes the Entity and signs the result.
Expand Down
18 changes: 13 additions & 5 deletions go/common/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,14 +394,22 @@ func (s *MultiSignedNode) Open(context signature.Context, node *Node) error {
// PrettyPrint writes a pretty-printed representation of the type
// to the given writer.
func (s MultiSignedNode) PrettyPrint(prefix string, w io.Writer) {
var n Node
if err := cbor.Unmarshal(s.MultiSigned.Blob, &n); err != nil {
fmt.Fprintf(w, "%s<malformed: %s>\n", prefix, err)
pt, err := s.PrettyType()
if err != nil {
fmt.Fprintf(w, "%s<error: %s>\n", prefix, err)
return
}

pp := signature.NewPrettyMultiSigned(s.MultiSigned, n)
pp.PrettyPrint(prefix, w)
pt.(prettyprint.PrettyPrinter).PrettyPrint(prefix, w)
}

// PrettyType returns a representation of the type that can be used for pretty printing.
func (s MultiSignedNode) PrettyType() (interface{}, error) {
var n Node
if err := cbor.Unmarshal(s.MultiSigned.Blob, &n); err != nil {
return nil, fmt.Errorf("malformed signed blob: %w", err)
}
return signature.NewPrettyMultiSigned(s.MultiSigned, n)
}

// MultiSignNode serializes the Node and multi-signs the result.
Expand Down
3 changes: 3 additions & 0 deletions go/common/prettyprint/prettyprint.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ type PrettyPrinter interface {
// PrettyPrint writes a pretty-printed representation of the type
// to the given writer.
PrettyPrint(prefix string, w io.Writer)

// PrettyType returns a representation of the type that can be used for pretty printing.
PrettyType() (interface{}, error)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package testvectors

import (
"reflect"
Expand All @@ -9,25 +9,13 @@ import (
"github.com/oasisprotocol/oasis-core/go/consensus/api/transaction"
)

const keySeedPrefix = "oasis-core staking test vectors: "

// TestTransactions is a test transaction suitable for JSON serialization.
//
// The only difference between this and transaction.Transaction is that
// body is not represented as raw bytes but rather as an interface so
// it contains the actual body document.
type TestTransaction struct {
Nonce uint64 `json:"nonce"`
Fee *transaction.Fee `json:"fee,omitempty"`
Method transaction.MethodName `json:"method"`
Body interface{} `json:"body,omitempty"`
}
const keySeedPrefix = "oasis-core test vectors: "

// TestVector is a staking message test vector.
type TestVector struct {
Kind string `json:"kind"`
SignatureContext string `json:"signature_context"`
Tx TestTransaction `json:"tx"`
Tx interface{} `json:"tx"`
SignedTx transaction.SignedTransaction `json:"signed_tx"`
EncodedTx []byte `json:"encoded_tx"`
EncodedSignedTx []byte `json:"encoded_signed_tx"`
Expand All @@ -36,8 +24,14 @@ type TestVector struct {
SignerPublicKey signature.PublicKey `json:"signer_public_key"`
}

func makeTestVector(kind string, tx *transaction.Transaction) TestVector {
// MakeTestVector generates a new test vector from a transction.
func MakeTestVector(kind string, tx *transaction.Transaction) TestVector {
signer := memorySigner.NewTestSigner(keySeedPrefix + kind)
return MakeTestVectorWithSigner(kind, tx, 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 {
sigTx, err := transaction.Sign(signer, tx)
if err != nil {
panic(err)
Expand All @@ -54,17 +48,15 @@ func makeTestVector(kind string, tx *transaction.Transaction) TestVector {
panic(err)
}

testTx := TestTransaction{
Nonce: tx.Nonce,
Fee: tx.Fee,
Method: tx.Method,
Body: v,
prettyTx, err := tx.PrettyType()
if err != nil {
panic(err)
}

return TestVector{
Kind: kind,
SignatureContext: string(sigCtx),
Tx: testTx,
Tx: prettyTx,
SignedTx: *sigTx,
EncodedTx: cbor.Marshal(tx),
EncodedSignedTx: cbor.Marshal(sigTx),
Expand Down
49 changes: 49 additions & 0 deletions go/consensus/api/transaction/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,35 @@ func (t Transaction) PrettyPrint(prefix string, w io.Writer) {
fmt.Fprintf(w, "%s %s\n", prefix, data)
}

// PrettyType returns a representation of the type that can be used for pretty printing.
func (t *Transaction) PrettyType() (interface{}, error) {
bodyType := t.Method.BodyType()
if bodyType == nil {
return nil, fmt.Errorf("unknown method body type")
}

// Deserialize into correct type.
body := reflect.New(reflect.TypeOf(bodyType)).Interface()
if err := cbor.Unmarshal(t.Body, body); err != nil {
return nil, fmt.Errorf("failed to unmarshal transaction body: %w", err)
}

// If the body type supports pretty printing, use that.
if pp, ok := body.(prettyprint.PrettyPrinter); ok {
var err error
if body, err = pp.PrettyType(); err != nil {
return nil, fmt.Errorf("failed to pretty print transaction body: %w", err)
}
}

return &PrettyTransaction{
Nonce: t.Nonce,
Fee: t.Fee,
Method: t.Method,
Body: body,
}, nil
}

// SanityCheck performs a basic sanity check on the transaction.
func (t *Transaction) SanityCheck() error {
return t.Method.SanityCheck()
Expand All @@ -106,6 +135,17 @@ func NewTransaction(nonce uint64, fee *Fee, method MethodName, body interface{})
}
}

// PrettyTransaction is used for pretty-printing transactions so that the actual content is
// displayed instead of the binary blob.
//
// It should only be used for pretty printing.
type PrettyTransaction struct {
Nonce uint64 `json:"nonce"`
Fee *Fee `json:"fee,omitempty"`
Method MethodName `json:"method"`
Body interface{} `json:"body,omitempty"`
}

// SignedTransaction is a signed transaction.
type SignedTransaction struct {
signature.Signed
Expand Down Expand Up @@ -142,6 +182,15 @@ func (s SignedTransaction) PrettyPrint(prefix string, w io.Writer) {
tx.PrettyPrint(prefix+" ", w)
}

// PrettyType returns a representation of the type that can be used for pretty printing.
func (s SignedTransaction) PrettyType() (interface{}, error) {
var tx Transaction
if err := cbor.Unmarshal(s.Blob, &tx); err != nil {
return nil, fmt.Errorf("malformed signed blob: %w", err)
}
return signature.NewPrettySigned(s.Signed, tx)
}

// Open first verifies the blob signature and then unmarshals the blob.
func (s *SignedTransaction) Open(tx *Transaction) error { // nolint: interfacer
return s.Signed.Open(SignatureContext, tx)
Expand Down
18 changes: 13 additions & 5 deletions go/registry/api/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,14 +317,22 @@ func (s *SignedRuntime) Open(context signature.Context, runtime *Runtime) error
// PrettyPrint writes a pretty-printed representation of the type
// to the given writer.
func (s SignedRuntime) PrettyPrint(prefix string, w io.Writer) {
var rt Runtime
if err := cbor.Unmarshal(s.Signed.Blob, &rt); err != nil {
fmt.Fprintf(w, "%s<malformed: %s>\n", prefix, err)
pt, err := s.PrettyType()
if err != nil {
fmt.Fprintf(w, "%s<error: %s>\n", prefix, err)
return
}

pp := signature.NewPrettySigned(s.Signed, rt)
pp.PrettyPrint(prefix, w)
pt.(prettyprint.PrettyPrinter).PrettyPrint(prefix, w)
}

// PrettyType returns a representation of the type that can be used for pretty printing.
func (s SignedRuntime) PrettyType() (interface{}, error) {
var rt Runtime
if err := cbor.Unmarshal(s.Signed.Blob, &rt); err != nil {
return nil, fmt.Errorf("malformed signed blob: %w", err)
}
return signature.NewPrettySigned(s.Signed, rt)
}

// SignRuntime serializes the Runtime and signs the result.
Expand Down
Loading