Skip to content

Commit

Permalink
warp: add "requirePrimaryNetworkSigners" precompile config (#1232)
Browse files Browse the repository at this point in the history
* warp: add "requirePrimaryNetworkSigners" precompile config

* unused

* passing ut for re-enable precompile

* simplify

* Update plugin/evm/vm_warp_test.go

Signed-off-by: Darioush Jalali <darioush.jalali@avalabs.org>

* reduce diff

* review comments

* refactor

* verified in ut

* minor refactor

* simplify

* Revert "simplify"

This reverts commit 74d4c70.

---------

Signed-off-by: Darioush Jalali <darioush.jalali@avalabs.org>
  • Loading branch information
darioush authored Aug 22, 2024
1 parent 1d688de commit b0abb3b
Show file tree
Hide file tree
Showing 8 changed files with 277 additions and 78 deletions.
23 changes: 20 additions & 3 deletions plugin/evm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ import (
"github.com/ava-labs/subnet-evm/plugin/evm/message"
"github.com/ava-labs/subnet-evm/trie/triedb/hashdb"

warpcontract "github.com/ava-labs/subnet-evm/precompile/contracts/warp"
"github.com/ava-labs/subnet-evm/rpc"
statesyncclient "github.com/ava-labs/subnet-evm/sync/client"
"github.com/ava-labs/subnet-evm/sync/client/stats"
"github.com/ava-labs/subnet-evm/trie"
"github.com/ava-labs/subnet-evm/warp"
"github.com/ava-labs/subnet-evm/warp/handlers"
warpValidators "github.com/ava-labs/subnet-evm/warp/validators"

// Force-load tracer engine to trigger registration
//
Expand Down Expand Up @@ -1019,8 +1019,7 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]http.Handler, error) {
}

if vm.config.WarpAPIEnabled {
validatorsState := warpValidators.NewState(vm.ctx)
if err := handler.RegisterName("warp", warp.NewAPI(vm.ctx.NetworkID, vm.ctx.SubnetID, vm.ctx.ChainID, validatorsState, vm.warpBackend, vm.client)); err != nil {
if err := handler.RegisterName("warp", warp.NewAPI(vm.ctx.NetworkID, vm.ctx.SubnetID, vm.ctx.ChainID, vm.ctx.ValidatorState, vm.warpBackend, vm.client, vm.requirePrimaryNetworkSigners)); err != nil {
return nil, err
}
enabledAPIs = append(enabledAPIs, "warp")
Expand Down Expand Up @@ -1067,6 +1066,24 @@ func (vm *VM) GetCurrentNonce(address common.Address) (uint64, error) {
return state.GetNonce(address), nil
}

// currentRules returns the chain rules for the current block.
func (vm *VM) currentRules() params.Rules {
header := vm.eth.APIBackend.CurrentHeader()
return vm.chainConfig.Rules(header.Number, header.Time)
}

// requirePrimaryNetworkSigners returns true if warp messages from the primary
// network must be signed by the primary network validators.
// This is necessary when the subnet is not validating the primary network.
func (vm *VM) requirePrimaryNetworkSigners() bool {
switch c := vm.currentRules().ActivePrecompiles[warpcontract.ContractAddress].(type) {
case *warpcontract.Config:
return c.RequirePrimaryNetworkSigners
default: // includes nil due to non-presence
return false
}
}

func (vm *VM) startContinuousProfiler() {
// If the profiler directory is empty, return immediately
// without creating or starting a continuous profiler.
Expand Down
226 changes: 190 additions & 36 deletions plugin/evm/vm_warp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import (
_ "embed"

"github.com/ava-labs/avalanchego/ids"
commonEng "github.com/ava-labs/avalanchego/snow/engine/common"
"github.com/ava-labs/avalanchego/snow/engine/snowman/block"
"github.com/ava-labs/avalanchego/snow/validators"
"github.com/ava-labs/avalanchego/snow/validators/validatorstest"
"github.com/ava-labs/avalanchego/upgrade"
avagoUtils "github.com/ava-labs/avalanchego/utils"
"github.com/ava-labs/avalanchego/utils/constants"
"github.com/ava-labs/avalanchego/utils/crypto/bls"
"github.com/ava-labs/avalanchego/utils/set"
"github.com/ava-labs/avalanchego/vms/components/chain"
Expand Down Expand Up @@ -45,6 +47,20 @@ var (
exampleWarpABI string
)

type warpMsgFrom int

const (
fromSubnet warpMsgFrom = iota
fromPrimary
)

type useWarpMsgSigners int

const (
signersSubnet useWarpMsgSigners = iota
signersPrimary
)

func TestSendWarpMessage(t *testing.T) {
require := require.New(t)
genesis := &core.Genesis{}
Expand Down Expand Up @@ -404,78 +420,201 @@ func TestReceiveWarpMessage(t *testing.T) {
genesis := &core.Genesis{}
require.NoError(genesis.UnmarshalJSON([]byte(genesisJSONDurango)))
genesis.Config.GenesisPrecompiles = params.Precompiles{
// Note that warp is enabled without RequirePrimaryNetworkSigners
// by default in the genesis configuration.
warp.ConfigKey: warp.NewDefaultConfig(utils.TimeToNewUint64(upgrade.InitiallyActiveTime)),
}
genesisJSON, err := genesis.MarshalJSON()
require.NoError(err)
issuer, vm, _, _ := GenesisVM(t, true, string(genesisJSON), "", "")

// disable warp so we can re-enable it with RequirePrimaryNetworkSigners
disableTime := upgrade.InitiallyActiveTime.Add(10 * time.Second)
disableConfig := warp.NewDisableConfig(utils.TimeToNewUint64(disableTime))

// re-enable warp with RequirePrimaryNetworkSigners
reEnableTime := disableTime.Add(10 * time.Second)
reEnableConfig := warp.NewConfig(
utils.TimeToNewUint64(reEnableTime),
0, // QuorumNumerator
true, // RequirePrimaryNetworkSigners
)

upgradeConfig := params.UpgradeConfig{
PrecompileUpgrades: []params.PrecompileUpgrade{
{Config: disableConfig},
{Config: reEnableConfig},
},
}
upgradeBytes, err := json.Marshal(upgradeConfig)
require.NoError(err)

issuer, vm, _, _ := GenesisVM(t, true, string(genesisJSON), "", string(upgradeBytes))

defer func() {
require.NoError(vm.Shutdown(context.Background()))
}()

acceptedLogsChan := make(chan []*types.Log, 10)
logsSub := vm.eth.APIBackend.SubscribeAcceptedLogsEvent(acceptedLogsChan)
defer logsSub.Unsubscribe()
type test struct {
name string
sourceChainID ids.ID
msgFrom warpMsgFrom
useSigners useWarpMsgSigners
blockTime time.Time
}

payloadData := avagoUtils.RandomBytes(100)
blockGap := 2 * time.Second // Build blocks with a gap. Blocks built too quickly will have high fees.
tests := []test{
{
name: "subnet message should be signed by subnet without RequirePrimaryNetworkSigners",
sourceChainID: vm.ctx.ChainID,
msgFrom: fromSubnet,
useSigners: signersSubnet,
blockTime: upgrade.InitiallyActiveTime,
},
{
name: "P-Chain message should be signed by subnet without RequirePrimaryNetworkSigners",
sourceChainID: constants.PlatformChainID,
msgFrom: fromPrimary,
useSigners: signersSubnet,
blockTime: upgrade.InitiallyActiveTime.Add(blockGap),
},
{
name: "C-Chain message should be signed by subnet without RequirePrimaryNetworkSigners",
sourceChainID: testCChainID,
msgFrom: fromPrimary,
useSigners: signersSubnet,
blockTime: upgrade.InitiallyActiveTime.Add(2 * blockGap),
},
// Note here we disable warp and re-enable it with RequirePrimaryNetworkSigners
// by using reEnableTime.
{
name: "subnet message should be signed by subnet with RequirePrimaryNetworkSigners (unimpacted)",
sourceChainID: vm.ctx.ChainID,
msgFrom: fromSubnet,
useSigners: signersSubnet,
blockTime: reEnableTime,
},
{
name: "P-Chain message should be signed by subnet with RequirePrimaryNetworkSigners (unimpacted)",
sourceChainID: constants.PlatformChainID,
msgFrom: fromPrimary,
useSigners: signersSubnet,
blockTime: reEnableTime.Add(blockGap),
},
{
name: "C-Chain message should be signed by primary with RequirePrimaryNetworkSigners (impacted)",
sourceChainID: testCChainID,
msgFrom: fromPrimary,
useSigners: signersPrimary,
blockTime: reEnableTime.Add(2 * blockGap),
},
}
// Note each test corresponds to a block, the tests must be ordered by block
// time and cannot, eg be run in parallel or a separate golang test.
for _, test := range tests {
testReceiveWarpMessage(
t, issuer, vm, test.sourceChainID, test.msgFrom, test.useSigners, test.blockTime,
)
}
}

func testReceiveWarpMessage(
t *testing.T, issuer chan commonEng.Message, vm *VM,
sourceChainID ids.ID,
msgFrom warpMsgFrom, useSigners useWarpMsgSigners,
blockTime time.Time,
) {
require := require.New(t)
payloadData := avagoUtils.RandomBytes(100)
addressedPayload, err := payload.NewAddressedCall(
testEthAddrs[0].Bytes(),
payloadData,
)
require.NoError(err)

vm.ctx.SubnetID = ids.GenerateTestID()
vm.ctx.NetworkID = testNetworkID
unsignedMessage, err := avalancheWarp.NewUnsignedMessage(
vm.ctx.NetworkID,
vm.ctx.ChainID,
sourceChainID,
addressedPayload.Bytes(),
)
require.NoError(err)

nodeID1 := ids.GenerateTestNodeID()
blsSecretKey1, err := bls.NewSecretKey()
require.NoError(err)
blsPublicKey1 := bls.PublicFromSecretKey(blsSecretKey1)
blsSignature1 := bls.Sign(blsSecretKey1, unsignedMessage.Bytes())
type signer struct {
networkID ids.ID
nodeID ids.NodeID
secret *bls.SecretKey
signature *bls.Signature
weight uint64
}
newSigner := func(networkID ids.ID, weight uint64) signer {
secret, err := bls.NewSecretKey()
require.NoError(err)
return signer{
networkID: networkID,
nodeID: ids.GenerateTestNodeID(),
secret: secret,
signature: bls.Sign(secret, unsignedMessage.Bytes()),
weight: weight,
}
}

nodeID2 := ids.GenerateTestNodeID()
blsSecretKey2, err := bls.NewSecretKey()
require.NoError(err)
blsPublicKey2 := bls.PublicFromSecretKey(blsSecretKey2)
blsSignature2 := bls.Sign(blsSecretKey2, unsignedMessage.Bytes())
primarySigners := []signer{
newSigner(constants.PrimaryNetworkID, 50),
newSigner(constants.PrimaryNetworkID, 50),
}
subnetSigners := []signer{
newSigner(vm.ctx.SubnetID, 50),
newSigner(vm.ctx.SubnetID, 50),
}
signers := subnetSigners
if useSigners == signersPrimary {
signers = primarySigners
}

blsAggregatedSignature, err := bls.AggregateSignatures([]*bls.Signature{blsSignature1, blsSignature2})
blsSignatures := make([]*bls.Signature, len(signers))
for i := range signers {
blsSignatures[i] = signers[i].signature
}
blsAggregatedSignature, err := bls.AggregateSignatures(blsSignatures)
require.NoError(err)

minimumValidPChainHeight := uint64(10)
getValidatorSetTestErr := errors.New("can't get validator set test error")

vm.ctx.ValidatorState = &validatorstest.State{
GetSubnetIDF: func(ctx context.Context, chainID ids.ID) (ids.ID, error) {
return ids.Empty, nil
if msgFrom == fromPrimary {
return constants.PrimaryNetworkID, nil
}
return vm.ctx.SubnetID, nil
},
GetValidatorSetF: func(ctx context.Context, height uint64, subnetID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) {
if height < minimumValidPChainHeight {
return nil, getValidatorSetTestErr
}
return map[ids.NodeID]*validators.GetValidatorOutput{
nodeID1: {
NodeID: nodeID1,
PublicKey: blsPublicKey1,
Weight: 50,
},
nodeID2: {
NodeID: nodeID2,
PublicKey: blsPublicKey2,
Weight: 50,
},
}, nil
signers := subnetSigners
if subnetID == constants.PrimaryNetworkID {
signers = primarySigners
}

vdrOutput := make(map[ids.NodeID]*validators.GetValidatorOutput)
for _, s := range signers {
vdrOutput[s.nodeID] = &validators.GetValidatorOutput{
NodeID: s.nodeID,
PublicKey: bls.PublicFromSecretKey(s.secret),
Weight: s.weight,
}
}
return vdrOutput, nil
},
}

signersBitSet := set.NewBits()
signersBitSet.Add(0)
signersBitSet.Add(1)
for i := range signers {
signersBitSet.Add(i)
}

warpSignature := &avalancheWarp.BitSetSignature{
Signers: signersBitSet.Bytes(),
Expand All @@ -495,7 +634,7 @@ func TestReceiveWarpMessage(t *testing.T) {
getVerifiedWarpMessageTx, err := types.SignTx(
predicate.NewPredicateTx(
vm.chainConfig.ChainID,
0,
vm.txPool.Nonce(testEthAddrs[0]),
&warp.Module.Address,
1_000_000,
big.NewInt(225*params.GWei),
Expand All @@ -519,12 +658,28 @@ func TestReceiveWarpMessage(t *testing.T) {
validProposerCtx := &block.Context{
PChainHeight: minimumValidPChainHeight,
}
vm.clock.Set(vm.clock.Time().Add(2 * time.Second))
vm.clock.Set(blockTime)
<-issuer

block2, err := vm.BuildBlockWithContext(context.Background(), validProposerCtx)
require.NoError(err)

// Require the block was built with a successful predicate result
ethBlock := block2.(*chain.BlockWrapper).Block.(*Block).ethBlock
headerPredicateResultsBytes, ok := predicate.GetPredicateResultBytes(ethBlock.Extra())
require.True(ok)
results, err := predicate.ParseResults(headerPredicateResultsBytes)
require.NoError(err)

// Predicate results encode the index of invalid warp messages in a bitset.
// An empty bitset indicates success.
txResultsBytes := results.GetResults(
getVerifiedWarpMessageTx.Hash(),
warp.ContractAddress,
)
bitset := set.BitsFromBytes(txResultsBytes)
require.Zero(bitset.Len()) // Empty bitset indicates success

block2VerifyWithCtx, ok := block2.(block.WithVerifyContext)
require.True(ok)
shouldVerifyWithCtx, err := block2VerifyWithCtx.ShouldVerifyWithContext(context.Background())
Expand All @@ -548,15 +703,14 @@ func TestReceiveWarpMessage(t *testing.T) {
require.NoError(block2.Accept(context.Background()))
vm.blockChain.DrainAcceptorQueue()

ethBlock := block2.(*chain.BlockWrapper).Block.(*Block).ethBlock
verifiedMessageReceipts := vm.blockChain.GetReceiptsByHash(ethBlock.Hash())
require.Len(verifiedMessageReceipts, 1)
verifiedMessageTxReceipt := verifiedMessageReceipts[0]
require.Equal(types.ReceiptStatusSuccessful, verifiedMessageTxReceipt.Status)

expectedOutput, err := warp.PackGetVerifiedWarpMessageOutput(warp.GetVerifiedWarpMessageOutput{
Message: warp.WarpMessage{
SourceChainID: common.Hash(vm.ctx.ChainID),
SourceChainID: common.Hash(sourceChainID),
OriginSenderAddress: testEthAddrs[0],
Payload: payloadData,
},
Expand Down
Loading

0 comments on commit b0abb3b

Please sign in to comment.