-
Notifications
You must be signed in to change notification settings - Fork 180
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
21 changed files
with
1,875 additions
and
1,311 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
Oops, something went wrong.