Skip to content

Commit

Permalink
Sign validator uptime warp msg (#1367)
Browse files Browse the repository at this point in the history
* Bump avalanchego to master

* always sign uptime messages (testing branch)

* nits

* cleanup

* assign to correct `err`

* fix handler

* move ValidatorUptime type to subnet-evm

* disable always signing

* implement on the type itself

* remove unneeded code

* fix ut

* add validator state

* add pausable uptime manager

* remove stuttering name

* rename state listener

* add uptime tracking to VM

* remove unused param

* add wg for update validators

* update state before network shutdown

* restart bootstrapping status in test

* add get validator to state

* rename uptime to validator

* fix mock state

* tests

* Update plugin/evm/validators/state.go

Co-authored-by: Darioush Jalali <darioush.jalali@avalabs.org>
Signed-off-by: Ceyhun Onur <ceyhunonur54@gmail.com>

* use update enum

* Update plugin/evm/validators/state.go

Co-authored-by: Darioush Jalali <darioush.jalali@avalabs.org>
Signed-off-by: Ceyhun Onur <ceyhunonur54@gmail.com>

* Update plugin/evm/validators/state.go

Co-authored-by: Darioush Jalali <darioush.jalali@avalabs.org>
Signed-off-by: Ceyhun Onur <ceyhunonur54@gmail.com>

* respond to comments

* update avalanchego dep branch

* reviews

* reword errs

* fix test changes

* fix upgrades after deactivating latest in context

* use test branch from avalanchego

* use branch commit for ava version

* update e2e ava version

* update avago dep

* remove extra line...

* export struct

* implement acp118 signer and verifier

* avoid revalidating in sign

* refactor warp backend to use acp118 handler

* prune warp db before backend init

* add cache tests

* remove uptime msg type

* add cache test

* fix linter

* add validator uptimes

* bump avago getcurrentvalidators branch

* rename get validator IDs to NodeIDs

* sign uptime warp msg base on uptime calculator

* add tests

* reviews

* conflict fix

* custom err msg

* add listener mock

* bump avago test branch

* remove config

* remove api changes

* Revert "remove api changes"

This reverts commit 8ef763f.

* remove wrapped cache

* use non-version db for validatorsDB

* remove errs from resume and pause

* check after stopping

* use expectedTime in tests

* reviews

* Update plugin/evm/vm.go

Co-authored-by: Darioush Jalali <darioush.jalali@avalabs.org>
Signed-off-by: Ceyhun Onur <ceyhunonur54@gmail.com>

* fix len

* fix tests

* update avago branch

* use ctx from utils

* add empty check for source address

* nits

* remove log

* disable validators api by default

* fix test context

* use interfaces from pkgs

* improve comments

* Uptime validation nits (#1378)

* add uptime warp example

* remove log

* nit unused interface

* add weight and isSov as fields

* use validator struct in AddValidator

* add comments to example file

* fix test

* add new fields to tests

---------

Signed-off-by: Ceyhun Onur <ceyhun.onur@avalabs.org>

* Update plugin/evm/validators/state.go

Co-authored-by: Michael Kaplan <55204436+michaelkaplan13@users.noreply.github.com>
Signed-off-by: Ceyhun Onur <ceyhunonur54@gmail.com>

* pass locker

* rename addresscall verifier fn

* new fields and refactorings

* add new fields

* merge nits

* fix linter

* clarify comments

* update comment

* remove getnodeID

* bump to poc branch

* enable validators API by default

* reviews

* reviews

* update comment

* update to avago master

* revert test change

---------

Signed-off-by: Ceyhun Onur <ceyhunonur54@gmail.com>
Signed-off-by: Ceyhun Onur <ceyhun.onur@avalabs.org>
Co-authored-by: Darioush Jalali <darioush.jalali@avalabs.org>
Co-authored-by: Michael Kaplan <55204436+michaelkaplan13@users.noreply.github.com>
  • Loading branch information
3 people authored Nov 13, 2024
1 parent fb40c50 commit f1d549c
Show file tree
Hide file tree
Showing 11 changed files with 428 additions and 26 deletions.
124 changes: 124 additions & 0 deletions examples/sign-uptime-message/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package main

import (
"context"
"log"
"net/netip"
"time"

"github.com/prometheus/client_golang/prometheus"
"google.golang.org/protobuf/proto"

"github.com/ava-labs/avalanchego/api/info"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/network/p2p"
"github.com/ava-labs/avalanchego/network/peer"
"github.com/ava-labs/avalanchego/proto/pb/sdk"
"github.com/ava-labs/avalanchego/snow/networking/router"
"github.com/ava-labs/avalanchego/utils/compression"
"github.com/ava-labs/avalanchego/utils/logging"
"github.com/ava-labs/avalanchego/vms/platformvm/warp"
"github.com/ava-labs/avalanchego/vms/platformvm/warp/payload"
"github.com/ava-labs/avalanchego/wallet/subnet/primary"
"github.com/ava-labs/subnet-evm/warp/messages"

p2pmessage "github.com/ava-labs/avalanchego/message"
)

// An example application demonstrating how to request a signature for
// an uptime message from a node running locally.
func main() {
uri := primary.LocalAPIURI
// The following IDs are placeholders and should be replaced with real values
// before running the code.
// The validationID is for the validation period that the uptime message is signed for.
validationID := ids.FromStringOrPanic("p3NUAY4PbcAnyCyvUTjGVjezNEQCdnVdfAbJcZScvKpxP5tJr")
// The sourceChainID is the ID of the chain.
sourceChainID := ids.FromStringOrPanic("2UZWB4xjNadRcHSpXarQoCryiVdcGWoT5w1dUztNfMKkAd2hJX")
reqUptime := uint64(3486)
infoClient := info.NewClient(uri)
networkID, err := infoClient.GetNetworkID(context.Background())
if err != nil {
log.Fatalf("failed to fetch network ID: %s\n", err)
}

validatorUptime, err := messages.NewValidatorUptime(validationID, reqUptime)
if err != nil {
log.Fatalf("failed to create validatorUptime message: %s\n", err)
}

addressedCall, err := payload.NewAddressedCall(
nil,
validatorUptime.Bytes(),
)
if err != nil {
log.Fatalf("failed to create AddressedCall message: %s\n", err)
}

unsignedWarp, err := warp.NewUnsignedMessage(
networkID,
sourceChainID,
addressedCall.Bytes(),
)
if err != nil {
log.Fatalf("failed to create unsigned Warp message: %s\n", err)
}

p, err := peer.StartTestPeer(
context.Background(),
netip.AddrPortFrom(
netip.AddrFrom4([4]byte{127, 0, 0, 1}),
9651,
),
networkID,
router.InboundHandlerFunc(func(_ context.Context, msg p2pmessage.InboundMessage) {
log.Printf("received %s: %s", msg.Op(), msg.Message())
}),
)
if err != nil {
log.Fatalf("failed to start peer: %s\n", err)
}

messageBuilder, err := p2pmessage.NewCreator(
logging.NoLog{},
prometheus.NewRegistry(),
compression.TypeZstd,
time.Hour,
)
if err != nil {
log.Fatalf("failed to create message builder: %s\n", err)
}

appRequestPayload, err := proto.Marshal(&sdk.SignatureRequest{
Message: unsignedWarp.Bytes(),
})
if err != nil {
log.Fatalf("failed to marshal SignatureRequest: %s\n", err)
}

appRequest, err := messageBuilder.AppRequest(
sourceChainID,
0,
time.Hour,
p2p.PrefixMessage(
p2p.ProtocolPrefix(p2p.SignatureRequestHandlerID),
appRequestPayload,
),
)
if err != nil {
log.Fatalf("failed to create AppRequest: %s\n", err)
}

p.Send(context.Background(), appRequest)

time.Sleep(5 * time.Second)

p.StartClose()
err = p.AwaitClosed(context.Background())
if err != nil {
log.Fatalf("failed to close peer: %s\n", err)
}
}
64 changes: 64 additions & 0 deletions plugin/evm/validators/validatorstest/noop_state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package validatorstest

import (
"time"

ids "github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/set"
"github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces"
)

var NoOpState interfaces.State = &noOpState{}

type noOpState struct{}

func (n *noOpState) GetStatus(vID ids.ID) (bool, error) { return false, nil }

func (n *noOpState) GetValidationIDs() set.Set[ids.ID] { return set.NewSet[ids.ID](0) }

func (n *noOpState) GetNodeIDs() set.Set[ids.NodeID] { return set.NewSet[ids.NodeID](0) }

func (n *noOpState) GetValidator(vID ids.ID) (interfaces.Validator, error) {
return interfaces.Validator{}, nil
}

func (n *noOpState) GetNodeID(vID ids.ID) (ids.NodeID, error) { return ids.NodeID{}, nil }

func (n *noOpState) AddValidator(vdr interfaces.Validator) error {
return nil
}

func (n *noOpState) UpdateValidator(vdr interfaces.Validator) error {
return nil
}

func (n *noOpState) DeleteValidator(vID ids.ID) error {
return nil
}
func (n *noOpState) WriteState() error { return nil }

func (n *noOpState) SetStatus(vID ids.ID, isActive bool) error { return nil }

func (n *noOpState) SetWeight(vID ids.ID, newWeight uint64) error { return nil }

func (n *noOpState) RegisterListener(interfaces.StateCallbackListener) {}

func (n *noOpState) GetUptime(
nodeID ids.NodeID,
) (upDuration time.Duration, lastUpdated time.Time, err error) {
return 0, time.Time{}, nil
}

func (n *noOpState) SetUptime(
nodeID ids.NodeID,
upDuration time.Duration,
lastUpdated time.Time,
) error {
return nil
}

func (n *noOpState) GetStartTime(
nodeID ids.NodeID,
) (startTime time.Time, err error) {
return time.Time{}, nil
}
3 changes: 3 additions & 0 deletions plugin/evm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,9 @@ func (vm *VM) Initialize(
vm.ctx.ChainID,
vm.ctx.WarpSigner,
vm,
vm.uptimeManager,
vm.validatorState,
vm.ctx.Lock.RLocker(),
vm.warpDB,
meteredCache,
offchainWarpMessages,
Expand Down
14 changes: 13 additions & 1 deletion warp/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ import (
"context"
"errors"
"fmt"
"sync"

"github.com/ava-labs/avalanchego/cache"
"github.com/ava-labs/avalanchego/database"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/network/p2p/acp118"
"github.com/ava-labs/avalanchego/snow/consensus/snowman"
"github.com/ava-labs/avalanchego/snow/uptime"
avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp"
"github.com/ava-labs/avalanchego/vms/platformvm/warp/payload"
"github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces"
"github.com/ethereum/go-ethereum/log"
)

Expand Down Expand Up @@ -56,6 +59,9 @@ type backend struct {
db database.Database
warpSigner avalancheWarp.Signer
blockClient BlockClient
uptimeCalculator uptime.Calculator
validatorState interfaces.State
stateLock sync.Locker
signatureCache cache.Cacher[ids.ID, []byte]
messageCache *cache.LRU[ids.ID, *avalancheWarp.UnsignedMessage]
offchainAddressedCallMsgs map[ids.ID]*avalancheWarp.UnsignedMessage
Expand All @@ -68,6 +74,9 @@ func NewBackend(
sourceChainID ids.ID,
warpSigner avalancheWarp.Signer,
blockClient BlockClient,
uptimeCalculator uptime.Calculator,
validatorsState interfaces.State,
stateLock sync.Locker,
db database.Database,
signatureCache cache.Cacher[ids.ID, []byte],
offchainMessages [][]byte,
Expand All @@ -79,6 +88,9 @@ func NewBackend(
warpSigner: warpSigner,
blockClient: blockClient,
signatureCache: signatureCache,
uptimeCalculator: uptimeCalculator,
validatorState: validatorsState,
stateLock: stateLock,
messageCache: &cache.LRU[ids.ID, *avalancheWarp.UnsignedMessage]{Size: messageCacheSize},
stats: newVerifierStats(),
offchainAddressedCallMsgs: make(map[ids.ID]*avalancheWarp.UnsignedMessage),
Expand Down Expand Up @@ -180,7 +192,7 @@ func (b *backend) GetMessage(messageID ids.ID) (*avalancheWarp.UnsignedMessage,

unsignedMessageBytes, err := b.db.Get(messageID[:])
if err != nil {
return nil, fmt.Errorf("failed to get warp message %s from db: %w", messageID.String(), err)
return nil, err
}

unsignedMessage, err := avalancheWarp.ParseUnsignedMessage(unsignedMessageBytes)
Expand Down
20 changes: 15 additions & 5 deletions warp/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@ package warp

import (
"context"
"sync"
"testing"

"github.com/ava-labs/avalanchego/cache"
"github.com/ava-labs/avalanchego/database"
"github.com/ava-labs/avalanchego/database/memdb"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/snow/uptime"
"github.com/ava-labs/avalanchego/utils"
"github.com/ava-labs/avalanchego/utils/crypto/bls"
avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp"
"github.com/ava-labs/avalanchego/vms/platformvm/warp/payload"
"github.com/ava-labs/subnet-evm/plugin/evm/validators/validatorstest"
"github.com/ava-labs/subnet-evm/warp/warptest"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -44,7 +48,7 @@ func TestAddAndGetValidMessage(t *testing.T) {
require.NoError(t, err)
warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID)
messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 500}
backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, db, messageSignatureCache, nil)
backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, uptime.NoOpCalculator, validatorstest.NoOpState, &sync.RWMutex{}, db, messageSignatureCache, nil)
require.NoError(t, err)

// Add testUnsignedMessage to the warp backend
Expand All @@ -67,7 +71,7 @@ func TestAddAndGetUnknownMessage(t *testing.T) {
require.NoError(t, err)
warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID)
messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 500}
backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, db, messageSignatureCache, nil)
backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, uptime.NoOpCalculator, validatorstest.NoOpState, &sync.RWMutex{}, db, messageSignatureCache, nil)
require.NoError(t, err)

// Try getting a signature for a message that was not added.
Expand All @@ -86,7 +90,7 @@ func TestGetBlockSignature(t *testing.T) {
require.NoError(err)
warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID)
messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 500}
backend, err := NewBackend(networkID, sourceChainID, warpSigner, blockClient, db, messageSignatureCache, nil)
backend, err := NewBackend(networkID, sourceChainID, warpSigner, blockClient, uptime.NoOpCalculator, validatorstest.NoOpState, &sync.RWMutex{}, db, messageSignatureCache, nil)
require.NoError(err)

blockHashPayload, err := payload.NewHash(blkID)
Expand All @@ -113,7 +117,7 @@ func TestZeroSizedCache(t *testing.T) {

// Verify zero sized cache works normally, because the lru cache will be initialized to size 1 for any size parameter <= 0.
messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 0}
backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, db, messageSignatureCache, nil)
backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, uptime.NoOpCalculator, validatorstest.NoOpState, &sync.RWMutex{}, db, messageSignatureCache, nil)
require.NoError(t, err)

// Add testUnsignedMessage to the warp backend
Expand Down Expand Up @@ -157,6 +161,12 @@ func TestOffChainMessages(t *testing.T) {
require.Equal(expectedSignatureBytes, signature[:])
},
},
"unknown message": {
check: func(require *require.Assertions, b Backend) {
_, err := b.GetMessage(testUnsignedMessage.ID())
require.ErrorIs(err, database.ErrNotFound)
},
},
"invalid message": {
offchainMessages: [][]byte{{1, 2, 3}},
err: errParsingOffChainMessage,
Expand All @@ -167,7 +177,7 @@ func TestOffChainMessages(t *testing.T) {
db := memdb.New()

messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 0}
backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, db, messageSignatureCache, test.offchainMessages)
backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, uptime.NoOpCalculator, validatorstest.NoOpState, &sync.RWMutex{}, db, messageSignatureCache, test.offchainMessages)
require.ErrorIs(err, test.err)
if test.check != nil {
test.check(require, backend)
Expand Down
7 changes: 6 additions & 1 deletion warp/handlers/signature_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import (
"github.com/ava-labs/avalanchego/cache"
"github.com/ava-labs/avalanchego/database/memdb"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/snow/uptime"
"github.com/ava-labs/avalanchego/utils/crypto/bls"
avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp"
"github.com/ava-labs/avalanchego/vms/platformvm/warp/payload"
"github.com/ava-labs/subnet-evm/plugin/evm/message"
"github.com/ava-labs/subnet-evm/plugin/evm/validators/validatorstest"
"github.com/ava-labs/subnet-evm/utils"
"github.com/ava-labs/subnet-evm/warp"
"github.com/ava-labs/subnet-evm/warp/warptest"
Expand All @@ -33,7 +35,7 @@ func TestMessageSignatureHandler(t *testing.T) {
require.NoError(t, err)

messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 100}
backend, err := warp.NewBackend(snowCtx.NetworkID, snowCtx.ChainID, warpSigner, warptest.EmptyBlockClient, database, messageSignatureCache, [][]byte{offchainMessage.Bytes()})
backend, err := warp.NewBackend(snowCtx.NetworkID, snowCtx.ChainID, warpSigner, warptest.EmptyBlockClient, uptime.NoOpCalculator, validatorstest.NoOpState, snowCtx.Lock.RLocker(), database, messageSignatureCache, [][]byte{offchainMessage.Bytes()})
require.NoError(t, err)

msg, err := avalancheWarp.NewUnsignedMessage(snowCtx.NetworkID, snowCtx.ChainID, []byte("test"))
Expand Down Expand Up @@ -139,6 +141,9 @@ func TestBlockSignatureHandler(t *testing.T) {
snowCtx.ChainID,
warpSigner,
blockClient,
uptime.NoOpCalculator,
validatorstest.NoOpState,
snowCtx.Lock.RLocker(),
database,
messageSignatureCache,
nil,
Expand Down
6 changes: 0 additions & 6 deletions warp/messages/payload.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,6 @@ type Payload interface {
initialize(b []byte)
}

// Signable is an optional interface that payloads can implement to allow
// on-the-fly signing of incoming messages by the warp backend.
type Signable interface {
VerifyMesssage(sourceAddress []byte) error
}

func Parse(bytes []byte) (Payload, error) {
var payload Payload
if _, err := Codec.Unmarshal(bytes, &payload); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion warp/messages/validator_uptime.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
// has been up for TotalUptime seconds.
type ValidatorUptime struct {
ValidationID ids.ID `serialize:"true"`
TotalUptime uint64 `serialize:"true"`
TotalUptime uint64 `serialize:"true"` // in seconds

bytes []byte
}
Expand Down
Loading

0 comments on commit f1d549c

Please sign in to comment.