Skip to content

Commit

Permalink
Merge #4522
Browse files Browse the repository at this point in the history
4522: [Access] Refactor converters into separate files r=peterargue a=peterargue

The grpc converter methods were previously in one large 1200 line file, making it difficult to work with. This PR splits them up into different files per type and adds unittests validating that converting to protobuf and back results in the same object.

Co-authored-by: Peter Argue <89119817+peterargue@users.noreply.github.com>
  • Loading branch information
bors[bot] and peterargue authored Jun 30, 2023
2 parents 487981d + 6191498 commit c910f10
Show file tree
Hide file tree
Showing 21 changed files with 1,875 additions and 1,311 deletions.
92 changes: 92 additions & 0 deletions engine/common/rpc/convert/accounts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package convert

import (
"github.com/onflow/flow/protobuf/go/flow/entities"

"github.com/onflow/flow-go/crypto"
"github.com/onflow/flow-go/crypto/hash"
"github.com/onflow/flow-go/model/flow"
)

// AccountToMessage converts a flow.Account to a protobuf message
func AccountToMessage(a *flow.Account) (*entities.Account, error) {
keys := make([]*entities.AccountKey, len(a.Keys))
for i, k := range a.Keys {
messageKey, err := AccountKeyToMessage(k)
if err != nil {
return nil, err
}
keys[i] = messageKey
}

return &entities.Account{
Address: a.Address.Bytes(),
Balance: a.Balance,
Code: nil,
Keys: keys,
Contracts: a.Contracts,
}, nil
}

// MessageToAccount converts a protobuf message to a flow.Account
func MessageToAccount(m *entities.Account) (*flow.Account, error) {
if m == nil {
return nil, ErrEmptyMessage
}

accountKeys := make([]flow.AccountPublicKey, len(m.GetKeys()))
for i, key := range m.GetKeys() {
accountKey, err := MessageToAccountKey(key)
if err != nil {
return nil, err
}

accountKeys[i] = *accountKey
}

return &flow.Account{
Address: flow.BytesToAddress(m.GetAddress()),
Balance: m.GetBalance(),
Keys: accountKeys,
Contracts: m.Contracts,
}, nil
}

// AccountKeyToMessage converts a flow.AccountPublicKey to a protobuf message
func AccountKeyToMessage(a flow.AccountPublicKey) (*entities.AccountKey, error) {
publicKey := a.PublicKey.Encode()
return &entities.AccountKey{
Index: uint32(a.Index),
PublicKey: publicKey,
SignAlgo: uint32(a.SignAlgo),
HashAlgo: uint32(a.HashAlgo),
Weight: uint32(a.Weight),
SequenceNumber: uint32(a.SeqNumber),
Revoked: a.Revoked,
}, nil
}

// MessageToAccountKey converts a protobuf message to a flow.AccountPublicKey
func MessageToAccountKey(m *entities.AccountKey) (*flow.AccountPublicKey, error) {
if m == nil {
return nil, ErrEmptyMessage
}

sigAlgo := crypto.SigningAlgorithm(m.GetSignAlgo())
hashAlgo := hash.HashingAlgorithm(m.GetHashAlgo())

publicKey, err := crypto.DecodePublicKey(sigAlgo, m.GetPublicKey())
if err != nil {
return nil, err
}

return &flow.AccountPublicKey{
Index: int(m.GetIndex()),
PublicKey: publicKey,
SignAlgo: sigAlgo,
HashAlgo: hashAlgo,
Weight: int(m.GetWeight()),
SeqNumber: uint64(m.GetSequenceNumber()),
Revoked: m.GetRevoked(),
}, nil
}
58 changes: 58 additions & 0 deletions engine/common/rpc/convert/accounts_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package convert_test

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/onflow/flow-go/crypto"
"github.com/onflow/flow-go/crypto/hash"
"github.com/onflow/flow-go/engine/common/rpc/convert"
"github.com/onflow/flow-go/fvm"
"github.com/onflow/flow-go/utils/unittest"
)

// TestConvertAccount tests that converting an account to and from a protobuf message results in
// the same account
func TestConvertAccount(t *testing.T) {
t.Parallel()

a, err := unittest.AccountFixture()
require.NoError(t, err)

key2, err := unittest.AccountKeyFixture(128, crypto.ECDSAP256, hash.SHA3_256)
require.NoError(t, err)

a.Keys = append(a.Keys, key2.PublicKey(500))

msg, err := convert.AccountToMessage(a)
require.NoError(t, err)

converted, err := convert.MessageToAccount(msg)
require.NoError(t, err)

assert.Equal(t, a, converted)
}

// TestConvertAccountKey tests that converting an account key to and from a protobuf message results
// in the same account key
func TestConvertAccountKey(t *testing.T) {
t.Parallel()

privateKey, _ := unittest.AccountKeyDefaultFixture()
accountKey := privateKey.PublicKey(fvm.AccountKeyWeightThreshold)

// Explicitly test if Revoked is properly converted
accountKey.Revoked = true

msg, err := convert.AccountKeyToMessage(accountKey)
assert.Nil(t, err)

converted, err := convert.MessageToAccountKey(msg)
assert.Nil(t, err)

assert.Equal(t, accountKey, *converted)
assert.Equal(t, accountKey.PublicKey, converted.PublicKey)
assert.Equal(t, accountKey.Revoked, converted.Revoked)
}
154 changes: 154 additions & 0 deletions engine/common/rpc/convert/blocks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package convert

import (
"fmt"

"github.com/onflow/flow/protobuf/go/flow/entities"
"google.golang.org/protobuf/types/known/timestamppb"

"github.com/onflow/flow-go/model/flow"
)

// BlockToMessage converts a flow.Block to a protobuf Block message.
// signerIDs is a precomputed list of signer IDs for the block based on the block's signer indicies.
func BlockToMessage(h *flow.Block, signerIDs flow.IdentifierList) (
*entities.Block,
error,
) {
id := h.ID()

parentID := h.Header.ParentID
t := timestamppb.New(h.Header.Timestamp)
cg := CollectionGuaranteesToMessages(h.Payload.Guarantees)

seals := BlockSealsToMessages(h.Payload.Seals)

execResults, err := ExecutionResultsToMessages(h.Payload.Results)
if err != nil {
return nil, err
}

blockHeader, err := BlockHeaderToMessage(h.Header, signerIDs)
if err != nil {
return nil, err
}

bh := entities.Block{
Id: id[:],
Height: h.Header.Height,
ParentId: parentID[:],
Timestamp: t,
CollectionGuarantees: cg,
BlockSeals: seals,
Signatures: [][]byte{h.Header.ParentVoterSigData},
ExecutionReceiptMetaList: ExecutionResultMetaListToMessages(h.Payload.Receipts),
ExecutionResultList: execResults,
BlockHeader: blockHeader,
}

return &bh, nil
}

// BlockToMessageLight converts a flow.Block to the light form of a protobuf Block message.
func BlockToMessageLight(h *flow.Block) *entities.Block {
id := h.ID()

parentID := h.Header.ParentID
t := timestamppb.New(h.Header.Timestamp)
cg := CollectionGuaranteesToMessages(h.Payload.Guarantees)

return &entities.Block{
Id: id[:],
Height: h.Header.Height,
ParentId: parentID[:],
Timestamp: t,
CollectionGuarantees: cg,
Signatures: [][]byte{h.Header.ParentVoterSigData},
}
}

// MessageToBlock converts a protobuf Block message to a flow.Block.
func MessageToBlock(m *entities.Block) (*flow.Block, error) {
payload, err := PayloadFromMessage(m)
if err != nil {
return nil, fmt.Errorf("failed to extract payload data from message: %w", err)
}
header, err := MessageToBlockHeader(m.BlockHeader)
if err != nil {
return nil, fmt.Errorf("failed to convert block header: %w", err)
}
return &flow.Block{
Header: header,
Payload: payload,
}, nil
}

// BlockSealToMessage converts a flow.Seal to a protobuf BlockSeal message.
func BlockSealToMessage(s *flow.Seal) *entities.BlockSeal {
id := s.BlockID
result := s.ResultID
return &entities.BlockSeal{
BlockId: id[:],
ExecutionReceiptId: result[:],
ExecutionReceiptSignatures: [][]byte{}, // filling seals signature with zero
FinalState: StateCommitmentToMessage(s.FinalState),
AggregatedApprovalSigs: AggregatedSignaturesToMessages(s.AggregatedApprovalSigs),
ResultId: IdentifierToMessage(s.ResultID),
}
}

// MessageToBlockSeal converts a protobuf BlockSeal message to a flow.Seal.
func MessageToBlockSeal(m *entities.BlockSeal) (*flow.Seal, error) {
finalState, err := MessageToStateCommitment(m.FinalState)
if err != nil {
return nil, fmt.Errorf("failed to convert message to block seal: %w", err)
}
return &flow.Seal{
BlockID: MessageToIdentifier(m.BlockId),
ResultID: MessageToIdentifier(m.ResultId),
FinalState: finalState,
AggregatedApprovalSigs: MessagesToAggregatedSignatures(m.AggregatedApprovalSigs),
}, nil
}

// BlockSealsToMessages converts a slice of flow.Seal to a slice of protobuf BlockSeal messages.
func BlockSealsToMessages(b []*flow.Seal) []*entities.BlockSeal {
seals := make([]*entities.BlockSeal, len(b))
for i, s := range b {
seals[i] = BlockSealToMessage(s)
}
return seals
}

// MessagesToBlockSeals converts a slice of protobuf BlockSeal messages to a slice of flow.Seal.
func MessagesToBlockSeals(m []*entities.BlockSeal) ([]*flow.Seal, error) {
seals := make([]*flow.Seal, len(m))
for i, s := range m {
msg, err := MessageToBlockSeal(s)
if err != nil {
return nil, err
}
seals[i] = msg
}
return seals, nil
}

// PayloadFromMessage converts a protobuf Block message to a flow.Payload.
func PayloadFromMessage(m *entities.Block) (*flow.Payload, error) {
cgs := MessagesToCollectionGuarantees(m.CollectionGuarantees)
seals, err := MessagesToBlockSeals(m.BlockSeals)
if err != nil {
return nil, err
}
receipts := MessagesToExecutionResultMetaList(m.ExecutionReceiptMetaList)
results, err := MessagesToExecutionResults(m.ExecutionResultList)
if err != nil {
return nil, err
}
return &flow.Payload{
Guarantees: cgs,
Seals: seals,
Receipts: receipts,
Results: results,
}, nil
}
65 changes: 65 additions & 0 deletions engine/common/rpc/convert/blocks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package convert_test

import (
"bytes"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/onflow/flow-go/engine/common/rpc/convert"
"github.com/onflow/flow-go/model/flow"
"github.com/onflow/flow-go/utils/unittest"
)

// TestConvertBlock tests that converting a block to and from a protobuf message results in the same
// block
func TestConvertBlock(t *testing.T) {
t.Parallel()

block := unittest.FullBlockFixture()
block.SetPayload(unittest.PayloadFixture(unittest.WithAllTheFixins))

signerIDs := unittest.IdentifierListFixture(5)

msg, err := convert.BlockToMessage(&block, signerIDs)
require.NoError(t, err)

converted, err := convert.MessageToBlock(msg)
require.NoError(t, err)

assert.Equal(t, block, *converted)
}

// TestConvertBlockLight tests that converting a block to its light form results in only the correct
// fields being set
func TestConvertBlockLight(t *testing.T) {
t.Parallel()

block := unittest.FullBlockFixture()
block.SetPayload(unittest.PayloadFixture(unittest.WithAllTheFixins))

msg := convert.BlockToMessageLight(&block)

// required fields are set
blockID := block.ID()
assert.Equal(t, 0, bytes.Compare(blockID[:], msg.Id))
assert.Equal(t, block.Header.Height, msg.Height)
assert.Equal(t, 0, bytes.Compare(block.Header.ParentID[:], msg.ParentId))
assert.Equal(t, block.Header.Timestamp, msg.Timestamp.AsTime())
assert.Equal(t, 0, bytes.Compare(block.Header.ParentVoterSigData, msg.Signatures[0]))

guarantees := []*flow.CollectionGuarantee{}
for _, g := range msg.CollectionGuarantees {
guarantee := convert.MessageToCollectionGuarantee(g)
guarantees = append(guarantees, guarantee)
}

assert.Equal(t, block.Payload.Guarantees, guarantees)

// all other fields are not
assert.Nil(t, msg.BlockHeader)
assert.Len(t, msg.BlockSeals, 0)
assert.Len(t, msg.ExecutionReceiptMetaList, 0)
assert.Len(t, msg.ExecutionResultList, 0)
}
Loading

0 comments on commit c910f10

Please sign in to comment.