-
Notifications
You must be signed in to change notification settings - Fork 244
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Warp backend interface and implementation (#452)
* base warp backend * add signature caching * add docs * error handling * pr fixes * hash unsigned message for key * quick pr fixes and merge * save signature instead of whole msg * use avaGO cache * rename warpBackend and docs * fix nits * Update plugin/evm/warp_backend.go * Update plugin/evm/warp_backend.go * fix pr nits * pr fixes and testing * type check for caching * fix imports * use memdb and remove extra test * remove unused * fix imports * saving message in db and pr fixes * update copyright * update backend variable naming * add comment about saving db vs cache
- Loading branch information
minghinmatthewlam
authored
Feb 1, 2023
1 parent
009c86f
commit 1955873
Showing
2 changed files
with
175 additions
and
0 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,87 @@ | ||
// (c) 2023, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
package warp | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/ava-labs/avalanchego/cache" | ||
"github.com/ava-labs/avalanchego/database" | ||
"github.com/ava-labs/avalanchego/ids" | ||
"github.com/ava-labs/avalanchego/snow" | ||
"github.com/ava-labs/avalanchego/utils/hashing" | ||
"github.com/ava-labs/avalanchego/vms/platformvm/teleporter" | ||
) | ||
|
||
var _ WarpBackend = &warpBackend{} | ||
|
||
// WarpBackend tracks signature eligible warp messages and provides an interface to fetch them. | ||
// The backend is also used to query for warp message signatures by the signature request handler. | ||
type WarpBackend interface { | ||
// AddMessage signs [unsignedMessage] and adds it to the warp backend database | ||
AddMessage(ctx context.Context, unsignedMessage *teleporter.UnsignedMessage) error | ||
|
||
// GetSignature returns the signature of the requested message hash. | ||
GetSignature(ctx context.Context, messageHash ids.ID) ([]byte, error) | ||
} | ||
|
||
// warpBackend implements WarpBackend, keeps track of warp messages, and generates message signatures. | ||
type warpBackend struct { | ||
db database.Database | ||
snowCtx *snow.Context | ||
signatureCache *cache.LRU | ||
} | ||
|
||
// NewWarpBackend creates a new WarpBackend, and initializes the signature cache and message tracking database. | ||
func NewWarpBackend(snowCtx *snow.Context, db database.Database, signatureCacheSize int) WarpBackend { | ||
return &warpBackend{ | ||
db: db, | ||
snowCtx: snowCtx, | ||
signatureCache: &cache.LRU{Size: signatureCacheSize}, | ||
} | ||
} | ||
|
||
func (w *warpBackend) AddMessage(ctx context.Context, unsignedMessage *teleporter.UnsignedMessage) error { | ||
messageID := hashing.ComputeHash256Array(unsignedMessage.Bytes()) | ||
|
||
// In the case when a node restarts, and possibly changes its bls key, the cache gets emptied but the database does not. | ||
// So to avoid having incorrect signatures saved in the database after a bls key change, we save the full message in the database. | ||
// Whereas for the cache, after the node restart, the cache would be emptied so we can directly save the signatures. | ||
if err := w.db.Put(messageID[:], unsignedMessage.Bytes()); err != nil { | ||
return fmt.Errorf("failed to put warp signature in db: %w", err) | ||
} | ||
|
||
signature, err := w.snowCtx.TeleporterSigner.Sign(unsignedMessage) | ||
if err != nil { | ||
return fmt.Errorf("failed to sign warp message: %w", err) | ||
} | ||
|
||
w.signatureCache.Put(ids.ID(messageID), signature) | ||
return nil | ||
} | ||
|
||
func (w *warpBackend) GetSignature(ctx context.Context, messageID ids.ID) ([]byte, error) { | ||
if sig, ok := w.signatureCache.Get(messageID); ok { | ||
return sig.([]byte), nil | ||
} | ||
|
||
unsignedMessageBytes, err := w.db.Get(messageID[:]) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to get warp message %s from db: %w", messageID.String(), err) | ||
} | ||
|
||
unsignedMessage, err := teleporter.ParseUnsignedMessage(unsignedMessageBytes) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse unsigned message %s: %w", messageID.String(), err) | ||
} | ||
|
||
signature, err := w.snowCtx.TeleporterSigner.Sign(unsignedMessage) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to sign warp message: %w", err) | ||
} | ||
|
||
w.signatureCache.Put(messageID[:], signature) | ||
return signature, 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,88 @@ | ||
// (c) 2023, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
package warp | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/ava-labs/avalanchego/database/memdb" | ||
"github.com/ava-labs/avalanchego/ids" | ||
"github.com/ava-labs/avalanchego/snow" | ||
"github.com/ava-labs/avalanchego/utils/crypto/bls" | ||
"github.com/ava-labs/avalanchego/utils/hashing" | ||
"github.com/ava-labs/avalanchego/vms/platformvm/teleporter" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
var ( | ||
sourceChainID = ids.GenerateTestID() | ||
destinationChainID = ids.GenerateTestID() | ||
payload = []byte("test") | ||
) | ||
|
||
func TestAddAndGetValidMessage(t *testing.T) { | ||
db := memdb.New() | ||
|
||
snowCtx := snow.DefaultContextTest() | ||
sk, err := bls.NewSecretKey() | ||
require.NoError(t, err) | ||
snowCtx.TeleporterSigner = teleporter.NewSigner(sk, sourceChainID) | ||
backend := NewWarpBackend(snowCtx, db, 500) | ||
|
||
// Create a new unsigned message and add it to the warp backend. | ||
unsignedMsg, err := teleporter.NewUnsignedMessage(sourceChainID, destinationChainID, payload) | ||
require.NoError(t, err) | ||
err = backend.AddMessage(context.Background(), unsignedMsg) | ||
require.NoError(t, err) | ||
|
||
// Verify that a signature is returned successfully, and compare to expected signature. | ||
messageID := hashing.ComputeHash256Array(unsignedMsg.Bytes()) | ||
signature, err := backend.GetSignature(context.Background(), messageID) | ||
require.NoError(t, err) | ||
|
||
expectedSig, err := snowCtx.TeleporterSigner.Sign(unsignedMsg) | ||
require.NoError(t, err) | ||
require.Equal(t, expectedSig, signature) | ||
} | ||
|
||
func TestAddAndGetUnknownMessage(t *testing.T) { | ||
db := memdb.New() | ||
|
||
backend := NewWarpBackend(snow.DefaultContextTest(), db, 500) | ||
unsignedMsg, err := teleporter.NewUnsignedMessage(sourceChainID, destinationChainID, payload) | ||
require.NoError(t, err) | ||
|
||
// Try getting a signature for a message that was not added. | ||
messageID := hashing.ComputeHash256Array(unsignedMsg.Bytes()) | ||
_, err = backend.GetSignature(context.Background(), messageID) | ||
require.Error(t, err) | ||
} | ||
|
||
func TestZeroSizedCache(t *testing.T) { | ||
db := memdb.New() | ||
|
||
snowCtx := snow.DefaultContextTest() | ||
sk, err := bls.NewSecretKey() | ||
require.NoError(t, err) | ||
snowCtx.TeleporterSigner = teleporter.NewSigner(sk, sourceChainID) | ||
|
||
// Verify zero sized cache works normally, because the lru cache will be initialized to size 1 for any size parameter <= 0. | ||
backend := NewWarpBackend(snowCtx, db, 0) | ||
|
||
// Create a new unsigned message and add it to the warp backend. | ||
unsignedMsg, err := teleporter.NewUnsignedMessage(sourceChainID, destinationChainID, payload) | ||
require.NoError(t, err) | ||
err = backend.AddMessage(context.Background(), unsignedMsg) | ||
require.NoError(t, err) | ||
|
||
// Verify that a signature is returned successfully, and compare to expected signature. | ||
messageID := hashing.ComputeHash256Array(unsignedMsg.Bytes()) | ||
signature, err := backend.GetSignature(context.Background(), messageID) | ||
require.NoError(t, err) | ||
|
||
expectedSig, err := snowCtx.TeleporterSigner.Sign(unsignedMsg) | ||
require.NoError(t, err) | ||
require.Equal(t, expectedSig, signature) | ||
} |