From c354ad6134898fd9554e16742651182f68329a34 Mon Sep 17 00:00:00 2001 From: minghinmatthewlam Date: Mon, 25 Sep 2023 10:08:22 -0700 Subject: [PATCH 01/52] Update IWarpMessenger interface name (#892) --- contracts/contracts/ExampleWarp.sol | 2 +- contracts/contracts/interfaces/IWarpMessenger.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/contracts/ExampleWarp.sol b/contracts/contracts/ExampleWarp.sol index 0a84abb376..726b00c1c4 100644 --- a/contracts/contracts/ExampleWarp.sol +++ b/contracts/contracts/ExampleWarp.sol @@ -6,7 +6,7 @@ import "./interfaces/IWarpMessenger.sol"; contract ExampleWarp { address constant WARP_ADDRESS = 0x0200000000000000000000000000000000000005; - WarpMessenger warp = WarpMessenger(WARP_ADDRESS); + IWarpMessenger warp = IWarpMessenger(WARP_ADDRESS); // sendWarpMessage sends a warp message to the specified destination chain and address pair containing the payload function sendWarpMessage( diff --git a/contracts/contracts/interfaces/IWarpMessenger.sol b/contracts/contracts/interfaces/IWarpMessenger.sol index 86d29c52c7..7cf925ee35 100644 --- a/contracts/contracts/interfaces/IWarpMessenger.sol +++ b/contracts/contracts/interfaces/IWarpMessenger.sol @@ -18,7 +18,7 @@ struct WarpBlockHash { bytes32 blockHash; } -interface WarpMessenger { +interface IWarpMessenger { event SendWarpMessage( bytes32 indexed destinationChainID, address indexed destinationAddress, From b742dcd191f43349511932d3bae5e47fa109aa72 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Tue, 26 Sep 2023 12:58:01 -0400 Subject: [PATCH 02/52] rename WarpAPI to API (#864) Co-authored-by: aaronbuchwald Co-authored-by: Darioush Jalali --- plugin/evm/vm.go | 2 +- warp/warp_client_fetcher.go | 8 ++++---- warp/warp_service.go | 18 +++++++++--------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 6483420ab7..304551988d 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -944,7 +944,7 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]*commonEng.HTTPHandler if vm.config.WarpAPIEnabled { warpAggregator := aggregator.NewAggregator(vm.ctx.SubnetID, warpValidators.NewState(vm.ctx), &aggregator.NetworkSigner{Client: vm.client}) - if err := handler.RegisterName("warp", warp.NewWarpAPI(vm.warpBackend, warpAggregator)); err != nil { + if err := handler.RegisterName("warp", warp.NewAPI(vm.warpBackend, warpAggregator)); err != nil { return nil, err } enabledAPIs = append(enabledAPIs, "warp") diff --git a/warp/warp_client_fetcher.go b/warp/warp_client_fetcher.go index fc1142f150..eba0bc7949 100644 --- a/warp/warp_client_fetcher.go +++ b/warp/warp_client_fetcher.go @@ -12,17 +12,17 @@ import ( avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" ) -type warpAPIFetcher struct { +type apiFetcher struct { clients map[ids.NodeID]Client } -func NewWarpAPIFetcher(clients map[ids.NodeID]Client) *warpAPIFetcher { - return &warpAPIFetcher{ +func NewAPIFetcher(clients map[ids.NodeID]Client) *apiFetcher { + return &apiFetcher{ clients: clients, } } -func (f *warpAPIFetcher) FetchWarpSignature(ctx context.Context, nodeID ids.NodeID, unsignedWarpMessage *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { +func (f *apiFetcher) FetchWarpSignature(ctx context.Context, nodeID ids.NodeID, unsignedWarpMessage *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { client, ok := f.clients[nodeID] if !ok { return nil, fmt.Errorf("no warp client for nodeID: %s", nodeID) diff --git a/warp/warp_service.go b/warp/warp_service.go index 28f08eaa0b..ee907ead30 100644 --- a/warp/warp_service.go +++ b/warp/warp_service.go @@ -12,22 +12,22 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ) -// WarpAPI introduces snowman specific functionality to the evm -type WarpAPI struct { +// API introduces snowman specific functionality to the evm +type API struct { backend Backend aggregator *aggregator.Aggregator } -func NewWarpAPI(backend Backend, aggregator *aggregator.Aggregator) *WarpAPI { - return &WarpAPI{ +func NewAPI(backend Backend, aggregator *aggregator.Aggregator) *API { + return &API{ backend: backend, aggregator: aggregator, } } // GetSignature returns the BLS signature associated with a messageID. -func (api *WarpAPI) GetSignature(ctx context.Context, messageID ids.ID) (hexutil.Bytes, error) { - signature, err := api.backend.GetSignature(messageID) +func (a *API) GetSignature(ctx context.Context, messageID ids.ID) (hexutil.Bytes, error) { + signature, err := a.backend.GetSignature(messageID) if err != nil { return nil, fmt.Errorf("failed to get signature for with error %w", err) } @@ -35,13 +35,13 @@ func (api *WarpAPI) GetSignature(ctx context.Context, messageID ids.ID) (hexutil } // GetAggregateSignature fetches the aggregate signature for the requested [messageID] -func (api *WarpAPI) GetAggregateSignature(ctx context.Context, messageID ids.ID, quorumNum uint64) (signedMessageBytes hexutil.Bytes, err error) { - unsignedMessage, err := api.backend.GetMessage(messageID) +func (a *API) GetAggregateSignature(ctx context.Context, messageID ids.ID, quorumNum uint64) (signedMessageBytes hexutil.Bytes, err error) { + unsignedMessage, err := a.backend.GetMessage(messageID) if err != nil { return nil, err } - signatureResult, err := api.aggregator.AggregateSignatures(ctx, unsignedMessage, quorumNum) + signatureResult, err := a.aggregator.AggregateSignatures(ctx, unsignedMessage, quorumNum) if err != nil { return nil, err } From 3d2e3ad9b1bfa47aba807884a870b8bdf83ccb0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0rfan=20Evrens?= Date: Wed, 27 Sep 2023 01:05:04 +0300 Subject: [PATCH 03/52] Update warp.json (#902) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed extra spaces. Signed-off-by: İrfan Evrens --- tests/precompile/genesis/warp.json | 84 +++++++++++++++--------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/tests/precompile/genesis/warp.json b/tests/precompile/genesis/warp.json index 5acbb87827..219d622ff4 100644 --- a/tests/precompile/genesis/warp.json +++ b/tests/precompile/genesis/warp.json @@ -1,46 +1,46 @@ { - "config": { - "chainId": 99999, - "homesteadBlock": 0, - "eip150Block": 0, - "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0, - "constantinopleBlock": 0, - "petersburgBlock": 0, - "istanbulBlock": 0, - "muirGlacierBlock": 0, - "feeConfig": { - "gasLimit": 20000000, - "minBaseFee": 1000000000, - "targetGas": 100000000, - "baseFeeChangeDenominator": 48, - "minBlockGasCost": 0, - "maxBlockGasCost": 10000000, - "targetBlockRate": 2, - "blockGasCostStep": 500000 - }, - "warpConfig": { - "blockTimestamp": 0 - } + "config": { + "chainId": 99999, + "homesteadBlock": 0, + "eip150Block": 0, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "feeConfig": { + "gasLimit": 20000000, + "minBaseFee": 1000000000, + "targetGas": 100000000, + "baseFeeChangeDenominator": 48, + "minBlockGasCost": 0, + "maxBlockGasCost": 10000000, + "targetBlockRate": 2, + "blockGasCostStep": 500000 }, - "alloc": { - "8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": { - "balance": "0x52B7D2DCC80CD2E4000000" - }, - "0x0Fa8EA536Be85F32724D57A37758761B86416123": { - "balance": "0x52B7D2DCC80CD2E4000000" - } + "warpConfig": { + "blockTimestamp": 0 + } + }, + "alloc": { + "8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": { + "balance": "0x52B7D2DCC80CD2E4000000" }, - "nonce": "0x0", - "timestamp": "0x0", - "extraData": "0x00", - "gasLimit": "0x1312D00", - "difficulty": "0x0", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase": "0x0000000000000000000000000000000000000000", - "number": "0x0", - "gasUsed": "0x0", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + "0x0Fa8EA536Be85F32724D57A37758761B86416123": { + "balance": "0x52B7D2DCC80CD2E4000000" + } + }, + "nonce": "0x0", + "timestamp": "0x0", + "extraData": "0x00", + "gasLimit": "0x1312D00", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" } From d72504db2aa8e7f3e497568f69a650c61c21318e Mon Sep 17 00:00:00 2001 From: tokikuch Date: Tue, 26 Sep 2023 15:09:31 -0700 Subject: [PATCH 04/52] core/bloombits: fix deadlock when matcher session hits an error (#899) When MatcherSession encounters an error, it attempts to close the session. Closing waits for all goroutines to finish, including the 'distributor'. However, the distributor will not exit until all requests have returned. This patch fixes the issue by delivering the (empty) result to the distributor before calling Close(). Co-authored-by: Darioush Jalali --- core/bloombits/matcher.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/bloombits/matcher.go b/core/bloombits/matcher.go index a979ffb3ef..9e1606667e 100644 --- a/core/bloombits/matcher.go +++ b/core/bloombits/matcher.go @@ -640,13 +640,16 @@ func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan request <- &Retrieval{Bit: bit, Sections: sections, Context: s.ctx} result := <-request + + // Deliver a result before s.Close() to avoid a deadlock + s.deliverSections(result.Bit, result.Sections, result.Bitsets) + if result.Error != nil { s.errLock.Lock() s.err = result.Error s.errLock.Unlock() s.Close() } - s.deliverSections(result.Bit, result.Sections, result.Bitsets) } } } From 55b1e8c2f035dd6866f4f4e686176e013a2d4179 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 26 Sep 2023 15:12:25 -0700 Subject: [PATCH 05/52] enforce warpIndex is <= MaxInt32 (#876) --- core/state/statedb.go | 4 ++-- core/vm/interface.go | 2 +- precompile/contract/interfaces.go | 2 +- x/warp/contract_test.go | 26 ++++++++++++++++++++++++++ x/warp/contract_warp_handler.go | 8 ++++++-- 5 files changed, 36 insertions(+), 6 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 1ca56cfc05..d3d48ca68e 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1261,12 +1261,12 @@ func (s *StateDB) GetTxHash() common.Hash { // GetPredicateStorageSlots(AddrA, 0) -> Predicate1 // GetPredicateStorageSlots(AddrB, 0) -> Predicate2 // GetPredicateStorageSlots(AddrA, 1) -> Predicate3 -func (s *StateDB) GetPredicateStorageSlots(address common.Address, index uint32) ([]byte, bool) { +func (s *StateDB) GetPredicateStorageSlots(address common.Address, index int) ([]byte, bool) { predicates, exists := s.predicateStorageSlots[address] if !exists { return nil, false } - if int(index) >= len(predicates) { + if index >= len(predicates) { return nil, false } return predicates[index], true diff --git a/core/vm/interface.go b/core/vm/interface.go index 8de415bfd6..21cdf8f62a 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -87,7 +87,7 @@ type StateDB interface { AddLog(addr common.Address, topics []common.Hash, data []byte, blockNumber uint64) GetLogData() [][]byte - GetPredicateStorageSlots(address common.Address, index uint32) ([]byte, bool) + GetPredicateStorageSlots(address common.Address, index int) ([]byte, bool) SetPredicateStorageSlots(address common.Address, predicates [][]byte) GetTxHash() common.Hash diff --git a/precompile/contract/interfaces.go b/precompile/contract/interfaces.go index cae08415a4..f3ce0a9e13 100644 --- a/precompile/contract/interfaces.go +++ b/precompile/contract/interfaces.go @@ -34,7 +34,7 @@ type StateDB interface { AddLog(addr common.Address, topics []common.Hash, data []byte, blockNumber uint64) GetLogData() [][]byte - GetPredicateStorageSlots(address common.Address, index uint32) ([]byte, bool) + GetPredicateStorageSlots(address common.Address, index int) ([]byte, bool) SetPredicateStorageSlots(address common.Address, predicates [][]byte) GetTxHash() common.Hash diff --git a/x/warp/contract_test.go b/x/warp/contract_test.go index 3b5efd4b14..3ba6b0f670 100644 --- a/x/warp/contract_test.go +++ b/x/warp/contract_test.go @@ -402,6 +402,19 @@ func TestGetVerifiedWarpMessage(t *testing.T) { ReadOnly: false, ExpectedErr: errInvalidIndexInput.Error(), }, + "get message index invalid int32": { + Caller: callerAddr, + InputFn: func(t testing.TB) []byte { + res, err := PackGetVerifiedWarpMessage(math.MaxInt32 + 1) + if err != nil { + t.Fatal(err) + } + return res + }, + SuppliedGas: GetVerifiedWarpMessageBaseCost, + ReadOnly: false, + ExpectedErr: errInvalidIndexInput.Error(), + }, "get message invalid index input bytes": { Caller: callerAddr, InputFn: func(t testing.TB) []byte { @@ -650,6 +663,19 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { ReadOnly: false, ExpectedErr: errInvalidIndexInput.Error(), }, + "get message index invalid int32": { + Caller: callerAddr, + InputFn: func(t testing.TB) []byte { + res, err := PackGetVerifiedWarpBlockHash(math.MaxInt32 + 1) + if err != nil { + t.Fatal(err) + } + return res + }, + SuppliedGas: GetVerifiedWarpMessageBaseCost, + ReadOnly: false, + ExpectedErr: errInvalidIndexInput.Error(), + }, "get message invalid index input bytes": { Caller: callerAddr, InputFn: func(t testing.TB) []byte { diff --git a/x/warp/contract_warp_handler.go b/x/warp/contract_warp_handler.go index 0a774fe291..7e9112e5d8 100644 --- a/x/warp/contract_warp_handler.go +++ b/x/warp/contract_warp_handler.go @@ -46,14 +46,18 @@ type messageHandler interface { } func handleWarpMessage(accessibleState contract.AccessibleState, input []byte, remainingGas uint64, handler messageHandler) ([]byte, uint64, error) { - warpIndex, err := UnpackGetVerifiedWarpMessageInput(input) + warpIndexInput, err := UnpackGetVerifiedWarpMessageInput(input) if err != nil { return nil, remainingGas, fmt.Errorf("%w: %s", errInvalidIndexInput, err) } + if warpIndexInput > math.MaxInt32 { + return nil, remainingGas, fmt.Errorf("%w: larger than MaxInt32", errInvalidIndexInput) + } + warpIndex := int(warpIndexInput) // This conversion is safe even if int is 32 bits because we checked above. state := accessibleState.GetStateDB() predicateBytes, exists := state.GetPredicateStorageSlots(ContractAddress, warpIndex) predicateResults := accessibleState.GetBlockContext().GetPredicateResults(state.GetTxHash(), ContractAddress) - valid := exists && set.BitsFromBytes(predicateResults).Contains(int(warpIndex)) + valid := exists && set.BitsFromBytes(predicateResults).Contains(warpIndex) if !valid { return handler.packFailed(), remainingGas, nil } From 0fc30c2a179822c318da5877bf950b8a9bc2d030 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 27 Sep 2023 01:26:16 +0300 Subject: [PATCH 06/52] verify warp is not activated before dupgrade (#872) * verify warp is not activated before dupgrade * Update x/warp/config.go Signed-off-by: Ceyhun Onur --- precompile/testutils/test_config.go | 13 ++++++++++++- x/warp/config.go | 12 ++++++++++-- x/warp/config_test.go | 9 +++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/precompile/testutils/test_config.go b/precompile/testutils/test_config.go index 6629ac8dec..2ecf8a1e25 100644 --- a/precompile/testutils/test_config.go +++ b/precompile/testutils/test_config.go @@ -6,8 +6,10 @@ package testutils import ( "testing" + "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" ) // ConfigVerifyTest is a test case for verifying a config @@ -30,7 +32,16 @@ func RunVerifyTests(t *testing.T, tests map[string]ConfigVerifyTest) { t.Helper() require := require.New(t) - err := test.Config.Verify(test.ChainConfig) + chainConfig := test.ChainConfig + if chainConfig == nil { + ctrl := gomock.NewController(t) + mockChainConfig := precompileconfig.NewMockChainConfig(ctrl) + mockChainConfig.EXPECT().GetFeeConfig().AnyTimes().Return(commontype.ValidTestFeeConfig) + mockChainConfig.EXPECT().AllowedFeeRecipients().AnyTimes().Return(false) + mockChainConfig.EXPECT().IsDUpgrade(gomock.Any()).AnyTimes().Return(true) + chainConfig = mockChainConfig + } + err := test.Config.Verify(chainConfig) if test.ExpectedError == "" { require.NoError(err) } else { diff --git a/x/warp/config.go b/x/warp/config.go index 9652183505..f91255c77c 100644 --- a/x/warp/config.go +++ b/x/warp/config.go @@ -32,6 +32,7 @@ var ( errInvalidAddressedPayload = errors.New("cannot unpack addressed payload") errInvalidBlockHashPayload = errors.New("cannot unpack block hash payload") errCannotGetNumSigners = errors.New("cannot fetch num signers from warp message") + errWarpCannotBeActivated = errors.New("warp cannot be activated before DUpgrade") ) // Config implements the precompileconfig.Config interface and @@ -72,8 +73,15 @@ func NewDisableConfig(blockTimestamp *uint64) *Config { func (*Config) Key() string { return ConfigKey } // Verify tries to verify Config and returns an error accordingly. -func (c *Config) Verify(precompileconfig.ChainConfig) error { - // TODO: return an error if Warp is enabled before DUpgrade +func (c *Config) Verify(chainConfig precompileconfig.ChainConfig) error { + if c.Timestamp() != nil { + // If Warp attempts to activate before the DUpgrade, fail verification + timestamp := *c.Timestamp() + if !chainConfig.IsDUpgrade(timestamp) { + return errWarpCannotBeActivated + } + } + if c.QuorumNumerator > params.WarpQuorumDenominator { return fmt.Errorf("cannot specify quorum numerator (%d) > quorum denominator (%d)", c.QuorumNumerator, params.WarpQuorumDenominator) } diff --git a/x/warp/config_test.go b/x/warp/config_test.go index 5cfcad8386..89dedf55af 100644 --- a/x/warp/config_test.go +++ b/x/warp/config_test.go @@ -33,6 +33,15 @@ func TestVerify(t *testing.T) { "valid quorum numerator 1 more than minimum": { Config: NewConfig(utils.NewUint64(3), params.WarpQuorumNumeratorMinimum+1), }, + "invalid cannot activated before DUpgrade activation": { + Config: NewConfig(utils.NewUint64(3), 0), + ChainConfig: func() precompileconfig.ChainConfig { + config := precompileconfig.NewMockChainConfig(gomock.NewController(t)) + config.EXPECT().IsDUpgrade(gomock.Any()).Return(false) + return config + }(), + ExpectedError: errWarpCannotBeActivated.Error(), + }, } testutils.RunVerifyTests(t, tests) } From 3c55f2e965c65f2c83853bcf538a49c50c74b766 Mon Sep 17 00:00:00 2001 From: Dhruba Basu <7675102+dhrubabasu@users.noreply.github.com> Date: Tue, 26 Sep 2023 18:46:34 -0400 Subject: [PATCH 07/52] Place stricter bounds on warp codec (#900) * Place stricter bounds on warp codec * Update warp/payload/codec.go --- warp/payload/codec.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/warp/payload/codec.go b/warp/payload/codec.go index b230bc2881..459506bf4d 100644 --- a/warp/payload/codec.go +++ b/warp/payload/codec.go @@ -5,23 +5,31 @@ package payload import ( "errors" - "math" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/codec/linearcodec" + "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/utils/wrappers" ) var errWrongType = errors.New("wrong payload type") -const codecVersion = 0 +const ( + codecVersion = 0 + + MaxMessageSize = 24 * units.KiB + + // Note: Modifying this variable can have subtle implications on memory + // usage when parsing malformed payloads. + MaxSliceLen = 24 * units.KiB +) // Codec does serialization and deserialization for Warp messages. var c codec.Manager func init() { - c = codec.NewManager(math.MaxInt) - lc := linearcodec.NewCustomMaxLength(math.MaxInt32) + c = codec.NewManager(MaxMessageSize) + lc := linearcodec.NewCustomMaxLength(MaxSliceLen) errs := wrappers.Errs{} errs.Add( From 69160eae4ac6a036b6359296925b182bb984bd23 Mon Sep 17 00:00:00 2001 From: minghinmatthewlam Date: Tue, 26 Sep 2023 17:39:06 -0700 Subject: [PATCH 08/52] Update preparePredicateStorageSlots doc (#891) * update preparePredicateStorageSlots doc * more specified comment for slice of byte slices --- core/state/statedb.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index d3d48ca68e..e5f414355d 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1197,10 +1197,9 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d } // preparePredicateStorageSlots populates the predicateStorageSlots field from the transaction's access list -// Note: if an address is specified multiple times in the access list, only the last storage slots provided -// for it are used in predicates. -// During predicate verification, we require that a precompile address is only specififed in the access list -// once to avoid a situation where we verify multiple predicate and only expose data from the last one. +// Note: if an address is specified multiple times in the access list, each storage slot for that address is +// appended to a slice of byte slices. Each byte slice represents a predicate, making it a slice of predicates +// for each access list address, and every predicate in the slice goes through verification. func (s *StateDB) preparePredicateStorageSlots(rules params.Rules, list types.AccessList) { s.predicateStorageSlots = make(map[common.Address][][]byte) for _, el := range list { From eb74d42be9ccb760b5aa09b9fc9af41f52542e26 Mon Sep 17 00:00:00 2001 From: minghinmatthewlam Date: Wed, 27 Sep 2023 01:25:57 -0700 Subject: [PATCH 09/52] Consolidate predicate prepare storage slots function (#908) * consolidate predicate prepare storage slots * add comments --- core/predicate_check.go | 13 +++---------- core/state/statedb.go | 16 +--------------- utils/predicate/predicate_slots.go | 26 ++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 25 deletions(-) create mode 100644 utils/predicate/predicate_slots.go diff --git a/core/predicate_check.go b/core/predicate_check.go index df7ed0a186..fc464b9921 100644 --- a/core/predicate_check.go +++ b/core/predicate_check.go @@ -31,16 +31,9 @@ func CheckPredicates(rules params.Rules, predicateContext *precompileconfig.Pred if len(rules.Predicates) == 0 { return predicateResults, nil } - predicateArguments := make(map[common.Address][][]byte) - for _, accessTuple := range tx.AccessList() { - address := accessTuple.Address - _, ok := rules.Predicates[address] - if !ok { - continue - } - - predicateArguments[address] = append(predicateArguments[address], predicateutils.HashSliceToBytes(accessTuple.StorageKeys)) - } + + // Prepare the predicate storage slots from the transaction's access list + predicateArguments := predicateutils.PreparePredicateStorageSlots(rules, tx.AccessList()) for address, predicates := range predicateArguments { // Since [address] is only added to [predicateArguments] when there's a valid predicate in the ruleset diff --git a/core/state/statedb.go b/core/state/statedb.go index e5f414355d..7c6abdbd33 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1190,26 +1190,12 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d al.AddAddress(coinbase) } - s.preparePredicateStorageSlots(rules, list) + s.predicateStorageSlots = predicateutils.PreparePredicateStorageSlots(rules, list) } // Reset transient storage at the beginning of transaction execution s.transientStorage = newTransientStorage() } -// preparePredicateStorageSlots populates the predicateStorageSlots field from the transaction's access list -// Note: if an address is specified multiple times in the access list, each storage slot for that address is -// appended to a slice of byte slices. Each byte slice represents a predicate, making it a slice of predicates -// for each access list address, and every predicate in the slice goes through verification. -func (s *StateDB) preparePredicateStorageSlots(rules params.Rules, list types.AccessList) { - s.predicateStorageSlots = make(map[common.Address][][]byte) - for _, el := range list { - if !rules.PredicateExists(el.Address) { - continue - } - s.predicateStorageSlots[el.Address] = append(s.predicateStorageSlots[el.Address], predicateutils.HashSliceToBytes(el.StorageKeys)) - } -} - // AddAddressToAccessList adds the given address to the access list func (s *StateDB) AddAddressToAccessList(addr common.Address) { if s.accessList.AddAddress(addr) { diff --git a/utils/predicate/predicate_slots.go b/utils/predicate/predicate_slots.go new file mode 100644 index 0000000000..6dbac08bb9 --- /dev/null +++ b/utils/predicate/predicate_slots.go @@ -0,0 +1,26 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package predicate + +import ( + "github.com/ava-labs/subnet-evm/core/types" + "github.com/ava-labs/subnet-evm/params" + "github.com/ethereum/go-ethereum/common" +) + +// PreparePredicateStorageSlots populates the the predicate storage slots of a transaction's access list +// Note: if an address is specified multiple times in the access list, each storage slot for that address is +// appended to a slice of byte slices. Each byte slice represents a predicate, making it a slice of predicates +// for each access list address, and every predicate in the slice goes through verification. +func PreparePredicateStorageSlots(rules params.Rules, list types.AccessList) map[common.Address][][]byte { + predicateStorageSlots := make(map[common.Address][][]byte) + for _, el := range list { + if !rules.PredicateExists(el.Address) { + continue + } + predicateStorageSlots[el.Address] = append(predicateStorageSlots[el.Address], HashSliceToBytes(el.StorageKeys)) + } + + return predicateStorageSlots +} From 991c1a22940a3cf7bdfec9f41640b0cc5ac92fa8 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 27 Sep 2023 04:34:49 -0700 Subject: [PATCH 10/52] SetTxPredicateResults: Consistent overwriting (#895) * SetTxPredicateResults: Consistent overwriting * add regression test --------- Co-authored-by: Ceyhun Onur --- precompile/results/predicate_results.go | 3 ++- precompile/results/predicate_results_test.go | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/precompile/results/predicate_results.go b/precompile/results/predicate_results.go index d9d17f0ebc..ed4e996e92 100644 --- a/precompile/results/predicate_results.go +++ b/precompile/results/predicate_results.go @@ -81,8 +81,9 @@ func (p *PredicateResults) GetPredicateResults(txHash common.Hash, address commo // SetTxPredicateResults sets the predicate results for the given [txHash]. Overrides results if present. func (p *PredicateResults) SetTxPredicateResults(txHash common.Hash, txResults TxPredicateResults) { - // If there are no tx results, omit them. + // If there are no tx results, don't store an entry in the map if len(txResults) == 0 { + delete(p.Results, txHash) return } p.Results[txHash] = txResults diff --git a/precompile/results/predicate_results_test.go b/precompile/results/predicate_results_test.go index 8eb49f42d8..22088c4b1f 100644 --- a/precompile/results/predicate_results_test.go +++ b/precompile/results/predicate_results_test.go @@ -119,4 +119,10 @@ func TestPredicateResultsAccessors(t *testing.T) { require.Equal(predicateResult, predicateResults.GetPredicateResults(txHash, addr)) predicateResults.DeleteTxPredicateResults(txHash) require.Empty(predicateResults.GetPredicateResults(txHash, addr)) + + // Ensure setting empty tx predicate results removes the entry + predicateResults.SetTxPredicateResults(txHash, txPredicateResults) + require.Equal(predicateResult, predicateResults.GetPredicateResults(txHash, addr)) + predicateResults.SetTxPredicateResults(txHash, map[common.Address][]byte{}) + require.Empty(predicateResults.GetPredicateResults(txHash, addr)) } From 4a9d9b56fa13aa14567a9e388764d3f2ac8e8bcc Mon Sep 17 00:00:00 2001 From: Alberto Benegiamo Date: Wed, 27 Sep 2023 15:42:27 +0200 Subject: [PATCH 11/52] removed code duplication (#912) --- x/warp/contract.go | 12 ++---------- x/warp/contract_warp_handler.go | 7 ++++++- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/x/warp/contract.go b/x/warp/contract.go index 76d0c94d4e..cb22b40aba 100644 --- a/x/warp/contract.go +++ b/x/warp/contract.go @@ -146,11 +146,7 @@ func UnpackGetVerifiedWarpBlockHashOutput(output []byte) (GetVerifiedWarpBlockHa } func getVerifiedWarpBlockHash(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - remainingGas, err = contract.DeductGas(suppliedGas, GetVerifiedWarpMessageBaseCost) - if err != nil { - return nil, remainingGas, err - } - return handleWarpMessage(accessibleState, input, remainingGas, blockHashHandler{}) + return handleWarpMessage(accessibleState, input, suppliedGas, blockHashHandler{}) } // UnpackGetVerifiedWarpMessageInput attempts to unpack [input] into the uint32 type argument @@ -192,11 +188,7 @@ func UnpackGetVerifiedWarpMessageOutput(output []byte) (GetVerifiedWarpMessageOu // getVerifiedWarpMessage retrieves the pre-verified warp message from the predicate storage slots and returns // the expected ABI encoding of the message to the caller. func getVerifiedWarpMessage(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - remainingGas, err = contract.DeductGas(suppliedGas, GetVerifiedWarpMessageBaseCost) - if err != nil { - return nil, remainingGas, err - } - return handleWarpMessage(accessibleState, input, remainingGas, addressedPayloadHandler{}) + return handleWarpMessage(accessibleState, input, suppliedGas, addressedPayloadHandler{}) } // UnpackSendWarpMessageInput attempts to unpack [input] as SendWarpMessageInput diff --git a/x/warp/contract_warp_handler.go b/x/warp/contract_warp_handler.go index 7e9112e5d8..7348f5c1fc 100644 --- a/x/warp/contract_warp_handler.go +++ b/x/warp/contract_warp_handler.go @@ -45,7 +45,12 @@ type messageHandler interface { handleMessage(msg *warp.Message) ([]byte, error) } -func handleWarpMessage(accessibleState contract.AccessibleState, input []byte, remainingGas uint64, handler messageHandler) ([]byte, uint64, error) { +func handleWarpMessage(accessibleState contract.AccessibleState, input []byte, suppliedGas uint64, handler messageHandler) ([]byte, uint64, error) { + remainingGas, err := contract.DeductGas(suppliedGas, GetVerifiedWarpMessageBaseCost) + if err != nil { + return nil, remainingGas, err + } + warpIndexInput, err := UnpackGetVerifiedWarpMessageInput(input) if err != nil { return nil, remainingGas, fmt.Errorf("%w: %s", errInvalidIndexInput, err) From 43cdb280c9cd03aa430dc60a268a789a0ef0b1c4 Mon Sep 17 00:00:00 2001 From: Alberto Benegiamo Date: Wed, 27 Sep 2023 16:00:42 +0200 Subject: [PATCH 12/52] Fixed config type cast (#913) * fixed config type cast * some more fixes * fix template --- .../bind/precompilebind/precompile_module_template.go | 11 ++++++++--- precompile/contracts/deployerallowlist/module.go | 2 +- precompile/contracts/feemanager/module.go | 2 +- precompile/contracts/nativeminter/module.go | 2 +- precompile/contracts/rewardmanager/module.go | 2 +- precompile/contracts/txallowlist/module.go | 2 +- x/warp/module.go | 5 ++--- 7 files changed, 15 insertions(+), 11 deletions(-) diff --git a/accounts/abi/bind/precompilebind/precompile_module_template.go b/accounts/abi/bind/precompilebind/precompile_module_template.go index 2af917b7d9..31d77383f1 100644 --- a/accounts/abi/bind/precompilebind/precompile_module_template.go +++ b/accounts/abi/bind/precompilebind/precompile_module_template.go @@ -60,15 +60,20 @@ func (*configurator) MakeConfig() precompileconfig.Config { // You can use this function to set up your precompile contract's initial state, // by using the [cfg] config and [state] stateDB. func (*configurator) Configure(chainConfig precompileconfig.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, blockContext contract.ConfigurationBlockContext) error { + // CUSTOM CODE STARTS HERE + {{- if .Contract.AllowList}} config, ok := cfg.(*Config) if !ok { - return fmt.Errorf("incorrect config %T: %v", config, config) + return fmt.Errorf("expected config type %T, got %T: %v", &Config{}, cfg, cfg) } - // CUSTOM CODE STARTS HERE - {{- if .Contract.AllowList}} + // AllowList is activated for this precompile. Configuring allowlist addresses here. return config.AllowListConfig.Configure(chainConfig, ContractAddress, state, blockContext) {{- else}} + if _, ok := cfg.(*Config); !ok { + return fmt.Errorf("expected config type %T, got %T: %v", &Config{}, cfg, cfg) + } + return nil {{- end}} } diff --git a/precompile/contracts/deployerallowlist/module.go b/precompile/contracts/deployerallowlist/module.go index 210d1c4e38..bdb0de8f18 100644 --- a/precompile/contracts/deployerallowlist/module.go +++ b/precompile/contracts/deployerallowlist/module.go @@ -43,7 +43,7 @@ func (*configurator) MakeConfig() precompileconfig.Config { func (c *configurator) Configure(chainConfig precompileconfig.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, blockContext contract.ConfigurationBlockContext) error { config, ok := cfg.(*Config) if !ok { - return fmt.Errorf("incorrect config %T: %v", config, config) + return fmt.Errorf("expected config type %T, got %T: %v", &Config{}, cfg, cfg) } return config.AllowListConfig.Configure(chainConfig, ContractAddress, state, blockContext) } diff --git a/precompile/contracts/feemanager/module.go b/precompile/contracts/feemanager/module.go index ab26e22f9d..8d0e1ea70d 100644 --- a/precompile/contracts/feemanager/module.go +++ b/precompile/contracts/feemanager/module.go @@ -43,7 +43,7 @@ func (*configurator) MakeConfig() precompileconfig.Config { func (*configurator) Configure(chainConfig precompileconfig.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, blockContext contract.ConfigurationBlockContext) error { config, ok := cfg.(*Config) if !ok { - return fmt.Errorf("incorrect config %T: %v", config, config) + return fmt.Errorf("expected config type %T, got %T: %v", &Config{}, cfg, cfg) } // Store the initial fee config into the state when the fee manager activates. if config.InitialFeeConfig != nil { diff --git a/precompile/contracts/nativeminter/module.go b/precompile/contracts/nativeminter/module.go index 9828f8d437..eb49cb1a23 100644 --- a/precompile/contracts/nativeminter/module.go +++ b/precompile/contracts/nativeminter/module.go @@ -44,7 +44,7 @@ func (*configurator) MakeConfig() precompileconfig.Config { func (*configurator) Configure(chainConfig precompileconfig.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, blockContext contract.ConfigurationBlockContext) error { config, ok := cfg.(*Config) if !ok { - return fmt.Errorf("incorrect config %T: %v", config, config) + return fmt.Errorf("expected config type %T, got %T: %v", &Config{}, cfg, cfg) } for to, amount := range config.InitialMint { if amount != nil { diff --git a/precompile/contracts/rewardmanager/module.go b/precompile/contracts/rewardmanager/module.go index 7f599de5df..ce8232a9ac 100644 --- a/precompile/contracts/rewardmanager/module.go +++ b/precompile/contracts/rewardmanager/module.go @@ -43,7 +43,7 @@ func (*configurator) MakeConfig() precompileconfig.Config { func (*configurator) Configure(chainConfig precompileconfig.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, blockContext contract.ConfigurationBlockContext) error { config, ok := cfg.(*Config) if !ok { - return fmt.Errorf("incorrect config %T: %v", config, config) + return fmt.Errorf("expected config type %T, got %T: %v", &Config{}, cfg, cfg) } // configure the RewardManager with the given initial configuration if config.InitialRewardConfig != nil { diff --git a/precompile/contracts/txallowlist/module.go b/precompile/contracts/txallowlist/module.go index 0c71e19c3c..3a87a12157 100644 --- a/precompile/contracts/txallowlist/module.go +++ b/precompile/contracts/txallowlist/module.go @@ -43,7 +43,7 @@ func (*configurator) MakeConfig() precompileconfig.Config { func (*configurator) Configure(chainConfig precompileconfig.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, blockContext contract.ConfigurationBlockContext) error { config, ok := cfg.(*Config) if !ok { - return fmt.Errorf("incorrect config %T: %v", config, config) + return fmt.Errorf("expected config type %T, got %T: %v", &Config{}, cfg, cfg) } return config.AllowListConfig.Configure(chainConfig, ContractAddress, state, blockContext) } diff --git a/x/warp/module.go b/x/warp/module.go index 84e15f578a..98d4278178 100644 --- a/x/warp/module.go +++ b/x/warp/module.go @@ -48,9 +48,8 @@ func (*configurator) MakeConfig() precompileconfig.Config { // Configure is a no-op for warp since it does not need to store any information in the state func (*configurator) Configure(chainConfig precompileconfig.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, _ contract.ConfigurationBlockContext) error { - config, ok := cfg.(*Config) - if !ok { - return fmt.Errorf("incorrect config %T: %v", config, config) + if _, ok := cfg.(*Config); !ok { + return fmt.Errorf("expected config type %T, got %T: %v", &Config{}, cfg, cfg) } return nil } From 2058696cfddea4995e6c0abd2d9af74827fc897e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0rfan=20Evrens?= Date: Wed, 27 Sep 2023 17:11:03 +0300 Subject: [PATCH 13/52] Fixed some golang links. (#911) --- .github/CONTRIBUTING.md | 6 +++--- README.md | 4 ++-- core/txpool/list.go | 2 +- metrics/README.md | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index d0c9fae378..1dc750fb60 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -18,8 +18,8 @@ Please make sure your contributions adhere to our coding and documentation guidelines: - Code must adhere to the official Go - [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines - (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). + [formatting](https://go.dev/doc/effective_go#formatting) guidelines + (i.e. uses [gofmt](https://pkg.go.dev/cmd/gofmt)). - Pull requests need to be based on and opened against the `master` branch. - Pull reuqests should include a detailed description - Commits are required to be signed. See [here](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits) @@ -32,7 +32,7 @@ guidelines: - Code should be well commented, so it is easier to read and maintain. Any complex sections or invariants should be documented explicitly. - Code must be documented adhering to the official Go - [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. + [commentary](https://go.dev/doc/effective_go#commentary) guidelines. - Changes with user facing impact (e.g., addition or modification of flags and options) should be accompanied by a link to a pull request to the [avalanche-docs](https://github.com/ava-labs/avalanche-docs) repository. [example](https://github.com/ava-labs/avalanche-docs/pull/1119/files). diff --git a/README.md b/README.md index a4cf6bd50b..aecd78a915 100644 --- a/README.md +++ b/README.md @@ -89,9 +89,9 @@ To support these changes, there have been a number of changes to the SubnetEVM b ### Clone Subnet-evm -First install Go 1.20.8 or later. Follow the instructions [here](https://golang.org/doc/install). You can verify by running `go version`. +First install Go 1.20.8 or later. Follow the instructions [here](https://go.dev/doc/install). You can verify by running `go version`. -Set `$GOPATH` environment variable properly for Go to look for Go Workspaces. Please read [this](https://go.dev/doc/gopath_code) for details. You can verify by running `echo $GOPATH`. +Set `$GOPATH` environment variable properly for Go to look for Go Workspaces. Please read [this](https://go.dev/doc/code) for details. You can verify by running `echo $GOPATH`. As a few software will be installed into `$GOPATH/bin`, please make sure that `$GOPATH/bin` is in your `$PATH`, otherwise, you may get error running the commands below. diff --git a/core/txpool/list.go b/core/txpool/list.go index 758b7f0fb0..0781e04ae5 100644 --- a/core/txpool/list.go +++ b/core/txpool/list.go @@ -520,7 +520,7 @@ type pricedList struct { // Number of stale price points to (re-heap trigger). // This field is accessed atomically, and must be the first field // to ensure it has correct alignment for atomic.AddInt64. - // See https://golang.org/pkg/sync/atomic/#pkg-note-BUG. + // See https://pkg.go.dev/sync/atomic#pkg-note-BUG. stales int64 all *lookup // Pointer to the map of all transactions diff --git a/metrics/README.md b/metrics/README.md index 0fbaabe4be..cf153c8093 100644 --- a/metrics/README.md +++ b/metrics/README.md @@ -5,7 +5,7 @@ go-metrics Go port of Coda Hale's Metrics library: . -Documentation: . +Documentation: . Usage ----- From e50dd840a10f9169e6b7353dc30d5802288913d5 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 27 Sep 2023 09:57:44 -0700 Subject: [PATCH 14/52] NewEVMBlockContext: more explicit err path (#897) * NewEVMBlockContext: more explicit err path * Update core/evm.go --- core/evm.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/core/evm.go b/core/evm.go index 3a349f44b1..9d016e4843 100644 --- a/core/evm.go +++ b/core/evm.go @@ -51,20 +51,21 @@ type ChainContext interface { // NewEVMBlockContext creates a new context for use in the EVM. func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext { - var ( - predicateResults *results.PredicateResults - err error - ) - if len(header.Extra) > params.DynamicFeeExtraDataSize { - // Prior to the DUpgrade, the VM enforces the extra data is smaller than or equal to this size. - // After the DUpgrade, the VM pre-verifies the extra data past the dynamic fee rollup window is - // valid. - predicateResults, err = results.ParsePredicateResults(header.Extra[params.DynamicFeeExtraDataSize:]) - if err != nil { - log.Error("failed to parse predicate results creating new block context", "err", err, "extra", header.Extra) - } + if len(header.Extra) <= params.DynamicFeeExtraDataSize { + return newEVMBlockContext(header, chain, author, nil) } + // Prior to the DUpgrade, the VM enforces the extra data is smaller than or + // equal to this size. After the DUpgrade, the VM pre-verifies the extra + // data past the dynamic fee rollup window is valid. + predicateResults, err := results.ParsePredicateResults(header.Extra[params.DynamicFeeExtraDataSize:]) + if err != nil { + log.Error("failed to parse predicate results creating new block context", "err", err, "extra", header.Extra) + // As mentioned above, we pre-verify the extra data to ensure this never happens. + // If we hit an error, construct a new block context rather than use a potentially half initialized value + // as defense in depth. + return newEVMBlockContext(header, chain, author, nil) + } return newEVMBlockContext(header, chain, author, predicateResults) } From 8e6f2f83e6b330050605bf21c389520964e86a2f Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 27 Sep 2023 13:49:25 -0400 Subject: [PATCH 15/52] Invalidate block when missing predicate context (#901) * Invalidate block when missing predicate context * Fix tests * Update core/predicate_check.go --- core/predicate_check.go | 10 ++++ core/predicate_check_test.go | 108 +++++++++++++++++++++++++---------- x/warp/config.go | 4 -- x/warp/predicate_test.go | 26 --------- 4 files changed, 88 insertions(+), 60 deletions(-) diff --git a/core/predicate_check.go b/core/predicate_check.go index fc464b9921..4c355c15be 100644 --- a/core/predicate_check.go +++ b/core/predicate_check.go @@ -4,6 +4,7 @@ package core import ( + "errors" "fmt" "github.com/ava-labs/subnet-evm/core/types" @@ -14,6 +15,8 @@ import ( "github.com/ethereum/go-ethereum/log" ) +var ErrMissingPredicateContext = errors.New("missing predicate context") + // CheckPredicates verifies the predicates of [tx] and returns the result. Returning an error invalidates the block. func CheckPredicates(rules params.Rules, predicateContext *precompileconfig.PredicateContext, tx *types.Transaction) (map[common.Address][]byte, error) { // Check that the transaction can cover its IntrinsicGas (including the gas required by the predicate) before @@ -35,6 +38,13 @@ func CheckPredicates(rules params.Rules, predicateContext *precompileconfig.Pred // Prepare the predicate storage slots from the transaction's access list predicateArguments := predicateutils.PreparePredicateStorageSlots(rules, tx.AccessList()) + // If there are no predicates to verify, return early and skip requiring the proposervm block + // context to be populated. + + if predicateContext == nil || predicateContext.ProposerVMBlockCtx == nil { + return nil, ErrMissingPredicateContext + } + for address, predicates := range predicateArguments { // Since [address] is only added to [predicateArguments] when there's a valid predicate in the ruleset // there's no need to check if the predicate exists here. diff --git a/core/predicate_check_test.go b/core/predicate_check_test.go index 61cef54c4b..b22e1d4f6a 100644 --- a/core/predicate_check_test.go +++ b/core/predicate_check_test.go @@ -19,6 +19,7 @@ import ( type predicateCheckTest struct { accessList types.AccessList gas uint64 + predicateContext *precompileconfig.PredicateContext createPredicates func(t testing.TB) map[common.Address]precompileconfig.Predicater expectedRes map[common.Address][]byte expectedErr error @@ -32,14 +33,27 @@ func TestCheckPredicate(t *testing.T) { addr4 := common.HexToAddress("0xdd") predicateResultBytes1 := []byte{1, 2, 3} predicateResultBytes2 := []byte{3, 2, 1} + predicateContext := &precompileconfig.PredicateContext{ + ProposerVMBlockCtx: &block.Context{ + PChainHeight: 10, + }, + } for name, test := range map[string]predicateCheckTest{ - "no predicates, no access list passes": { - gas: 53000, - expectedRes: make(map[common.Address][]byte), - expectedErr: nil, + "no predicates, no access list, no context passes": { + gas: 53000, + predicateContext: nil, + expectedRes: make(map[common.Address][]byte), + expectedErr: nil, + }, + "no predicates, no access list, with context passes": { + gas: 53000, + predicateContext: predicateContext, + expectedRes: make(map[common.Address][]byte), + expectedErr: nil, }, - "no predicates, with access list passes": { - gas: 57300, + "no predicates, with access list, no context passes": { + gas: 57300, + predicateContext: nil, accessList: types.AccessList([]types.AccessTuple{ { Address: addr1, @@ -51,8 +65,9 @@ func TestCheckPredicate(t *testing.T) { expectedRes: make(map[common.Address][]byte), expectedErr: nil, }, - "predicate no access list passes": { - gas: 53000, + "predicate, no access list, no context passes": { + gas: 53000, + predicateContext: nil, createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater { predicate := precompileconfig.NewMockPredicater(gomock.NewController(t)) return map[common.Address]precompileconfig.Predicater{ @@ -62,13 +77,27 @@ func TestCheckPredicate(t *testing.T) { expectedRes: make(map[common.Address][]byte), expectedErr: nil, }, - "predicate named by access list returns empty": { + "predicate, no access list, no block context passes": { gas: 53000, + predicateContext: &precompileconfig.PredicateContext{ + ProposerVMBlockCtx: nil, + }, + createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater { + predicate := precompileconfig.NewMockPredicater(gomock.NewController(t)) + return map[common.Address]precompileconfig.Predicater{ + addr1: predicate, + } + }, + expectedRes: make(map[common.Address][]byte), + expectedErr: nil, + }, + "predicate named by access list, without context errors": { + gas: 53000, + predicateContext: nil, createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater { predicate := precompileconfig.NewMockPredicater(gomock.NewController(t)) arg := common.Hash{1} - predicate.EXPECT().PredicateGas(arg[:]).Return(uint64(0), nil).Times(2) - predicate.EXPECT().VerifyPredicate(gomock.Any(), [][]byte{arg[:]}).Return(nil) + predicate.EXPECT().PredicateGas(arg[:]).Return(uint64(0), nil).Times(1) return map[common.Address]precompileconfig.Predicater{ addr1: predicate, } @@ -81,13 +110,34 @@ func TestCheckPredicate(t *testing.T) { }, }, }), - expectedRes: map[common.Address][]byte{ - addr1: nil, + expectedErr: ErrMissingPredicateContext, + }, + "predicate named by access list, without block context errors": { + gas: 53000, + predicateContext: &precompileconfig.PredicateContext{ + ProposerVMBlockCtx: nil, }, - expectedErr: nil, + createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater { + predicate := precompileconfig.NewMockPredicater(gomock.NewController(t)) + arg := common.Hash{1} + predicate.EXPECT().PredicateGas(arg[:]).Return(uint64(0), nil).Times(1) + return map[common.Address]precompileconfig.Predicater{ + addr1: predicate, + } + }, + accessList: types.AccessList([]types.AccessTuple{ + { + Address: addr1, + StorageKeys: []common.Hash{ + {1}, + }, + }, + }), + expectedErr: ErrMissingPredicateContext, }, "predicate named by access list returns non-empty": { - gas: 53000, + gas: 53000, + predicateContext: predicateContext, createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater { predicate := precompileconfig.NewMockPredicater(gomock.NewController(t)) arg := common.Hash{1} @@ -111,7 +161,8 @@ func TestCheckPredicate(t *testing.T) { expectedErr: nil, }, "predicate returns gas err": { - gas: 53000, + gas: 53000, + predicateContext: predicateContext, createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater { predicate := precompileconfig.NewMockPredicater(gomock.NewController(t)) arg := common.Hash{1} @@ -131,7 +182,8 @@ func TestCheckPredicate(t *testing.T) { expectedErr: testErr, }, "two predicates one named by access list returns non-empty": { - gas: 53000, + gas: 53000, + predicateContext: predicateContext, createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater { predicate := precompileconfig.NewMockPredicater(gomock.NewController(t)) arg := common.Hash{1} @@ -156,7 +208,8 @@ func TestCheckPredicate(t *testing.T) { expectedErr: nil, }, "two predicates both named by access list returns non-empty": { - gas: 53000, + gas: 53000, + predicateContext: predicateContext, createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater { ctrl := gomock.NewController(t) predicate1 := precompileconfig.NewMockPredicater(ctrl) @@ -193,7 +246,8 @@ func TestCheckPredicate(t *testing.T) { expectedErr: nil, }, "two predicates niether named by access list": { - gas: 61600, + gas: 61600, + predicateContext: predicateContext, createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater { predicate := precompileconfig.NewMockPredicater(gomock.NewController(t)) return map[common.Address]precompileconfig.Predicater{ @@ -219,7 +273,8 @@ func TestCheckPredicate(t *testing.T) { expectedErr: nil, }, "insufficient gas": { - gas: 53000, + gas: 53000, + predicateContext: predicateContext, createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater { predicate := precompileconfig.NewMockPredicater(gomock.NewController(t)) arg := common.Hash{1} @@ -255,16 +310,9 @@ func TestCheckPredicate(t *testing.T) { AccessList: test.accessList, Gas: test.gas, }) - predicateContext := &precompileconfig.PredicateContext{ - ProposerVMBlockCtx: &block.Context{ - PChainHeight: 10, - }, - } - predicateRes, err := CheckPredicates(rules, predicateContext, tx) - if test.expectedErr == nil { - require.NoError(err) - } else { - require.ErrorIs(err, test.expectedErr) + predicateRes, err := CheckPredicates(rules, test.predicateContext, tx) + require.ErrorIs(err, test.expectedErr) + if test.expectedErr != nil { return } require.Equal(test.expectedRes, predicateRes) diff --git a/x/warp/config.go b/x/warp/config.go index f91255c77c..4fe982ada9 100644 --- a/x/warp/config.go +++ b/x/warp/config.go @@ -184,10 +184,6 @@ func (c *Config) PredicateGas(predicateBytes []byte) (uint64, error) { } func (c *Config) verifyPredicate(predicateContext *precompileconfig.PredicateContext, predicateBytes []byte) bool { - if predicateContext == nil || predicateContext.ProposerVMBlockCtx == nil { - return false - } - unpackedPredicateBytes, err := predicateutils.UnpackPredicate(predicateBytes) if err != nil { return false diff --git a/x/warp/predicate_test.go b/x/warp/predicate_test.go index 0113f65a55..96b0079fc1 100644 --- a/x/warp/predicate_test.go +++ b/x/warp/predicate_test.go @@ -230,32 +230,6 @@ func createValidPredicateTest(snowCtx *snow.Context, numKeys uint64, predicateBy } } -func TestWarpNilProposerCtx(t *testing.T) { - numKeys := 1 - snowCtx := createSnowCtx([]validatorRange{ - { - start: 0, - end: numKeys, - weight: 20, - publicKey: true, - }, - }) - predicateBytes := createPredicate(numKeys) - test := testutils.PredicateTest{ - Config: NewDefaultConfig(subnetEVMUtils.NewUint64(0)), - PredicateContext: &precompileconfig.PredicateContext{ - SnowCtx: snowCtx, - ProposerVMBlockCtx: nil, - }, - StorageSlots: [][]byte{predicateBytes}, - Gas: GasCostPerSignatureVerification + uint64(len(predicateBytes))*GasCostPerWarpMessageBytes + uint64(numKeys)*GasCostPerWarpSigner, - GasErr: nil, - PredicateRes: set.NewBits().Bytes(), - } - - test.Run(t) -} - func TestWarpMessageFromPrimaryNetwork(t *testing.T) { require := require.New(t) numKeys := 10 From 23b0e9cd83f08737f63464bc4755ea5cee17bc27 Mon Sep 17 00:00:00 2001 From: Alberto Benegiamo Date: Wed, 27 Sep 2023 19:56:02 +0200 Subject: [PATCH 16/52] Change from parsing predicate bytes to checking they equal expected value (#914) Signed-off-by: Alberto Benegiamo --- plugin/evm/block.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/plugin/evm/block.go b/plugin/evm/block.go index 1172f7ed8e..ae126790e5 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -294,11 +294,7 @@ func (b *Block) verifyPredicates(predicateContext *precompileconfig.PredicateCon } headerPredicateResultsBytes := extraData[params.DynamicFeeExtraDataSize:] if !bytes.Equal(headerPredicateResultsBytes, predicateResultsBytes) { - parsedResults, err := results.ParsePredicateResults(headerPredicateResultsBytes) - if err != nil { - return err - } - return fmt.Errorf("%w (remote: %x %v local: %x %v)", errInvalidHeaderPredicateResults, headerPredicateResultsBytes, parsedResults, predicateResultsBytes, predicateResults) + return fmt.Errorf("%w (remote: %x local: %x)", errInvalidHeaderPredicateResults, headerPredicateResultsBytes, predicateResultsBytes) } return nil } From 25e1196152dc2a60f003cb2ad8dc160ff5c330c3 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 28 Sep 2023 16:06:37 +0300 Subject: [PATCH 17/52] add early return (#916) --- core/predicate_check.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/predicate_check.go b/core/predicate_check.go index 4c355c15be..79968bd9e3 100644 --- a/core/predicate_check.go +++ b/core/predicate_check.go @@ -40,6 +40,9 @@ func CheckPredicates(rules params.Rules, predicateContext *precompileconfig.Pred // If there are no predicates to verify, return early and skip requiring the proposervm block // context to be populated. + if len(predicateArguments) == 0 { + return predicateResults, nil + } if predicateContext == nil || predicateContext.ProposerVMBlockCtx == nil { return nil, ErrMissingPredicateContext From 80faae7e3f6cbc8575b9a8ffe01599608d4370dd Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 28 Sep 2023 17:04:16 +0300 Subject: [PATCH 18/52] add get predicate result bytes helpers (#906) * add get predicate result bytes helpers * typed errs * add comment --- consensus/dummy/consensus.go | 9 +++------ core/evm.go | 8 ++++---- miner/worker.go | 4 ++-- plugin/evm/block.go | 7 ++++--- utils/predicate/predicate_bytes.go | 26 +++++++++++++++++++++++--- 5 files changed, 36 insertions(+), 18 deletions(-) diff --git a/consensus/dummy/consensus.go b/consensus/dummy/consensus.go index abe916ced0..1f77096179 100644 --- a/consensus/dummy/consensus.go +++ b/consensus/dummy/consensus.go @@ -192,9 +192,7 @@ func (self *DummyEngine) verifyHeaderGasFields(config *params.ChainConfig, heade // modified from consensus.go func (self *DummyEngine) verifyHeader(chain consensus.ChainHeaderReader, header *types.Header, parent *types.Header, uncle bool) error { - var ( - config = chain.Config() - ) + config := chain.Config() // Ensure that we do not verify an uncle if uncle { return errUnclesUnsupported @@ -205,9 +203,8 @@ func (self *DummyEngine) verifyHeader(chain consensus.ChainHeaderReader, header return fmt.Errorf("expected extra-data field length >= %d, found %d", params.DynamicFeeExtraDataSize, len(header.Extra)) } case config.IsSubnetEVM(header.Time): - expectedExtraDataSize := params.DynamicFeeExtraDataSize - if len(header.Extra) != expectedExtraDataSize { - return fmt.Errorf("expected extra-data field to be: %d, but found %d", expectedExtraDataSize, len(header.Extra)) + if len(header.Extra) != params.DynamicFeeExtraDataSize { + return fmt.Errorf("expected extra-data field to be: %d, but found %d", params.DynamicFeeExtraDataSize, len(header.Extra)) } default: if uint64(len(header.Extra)) > params.MaximumExtraDataSize { diff --git a/core/evm.go b/core/evm.go index 9d016e4843..f2b3ae3d55 100644 --- a/core/evm.go +++ b/core/evm.go @@ -32,8 +32,8 @@ import ( "github.com/ava-labs/subnet-evm/consensus" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" - "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile/results" + "github.com/ava-labs/subnet-evm/utils/predicate" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" //"github.com/ethereum/go-ethereum/log" @@ -51,14 +51,14 @@ type ChainContext interface { // NewEVMBlockContext creates a new context for use in the EVM. func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext { - if len(header.Extra) <= params.DynamicFeeExtraDataSize { + predicateBytes, err := predicate.GetPredicateResultBytes(header.Extra) + if err != nil { return newEVMBlockContext(header, chain, author, nil) } - // Prior to the DUpgrade, the VM enforces the extra data is smaller than or // equal to this size. After the DUpgrade, the VM pre-verifies the extra // data past the dynamic fee rollup window is valid. - predicateResults, err := results.ParsePredicateResults(header.Extra[params.DynamicFeeExtraDataSize:]) + predicateResults, err := results.ParsePredicateResults(predicateBytes) if err != nil { log.Error("failed to parse predicate results creating new block context", "err", err, "extra", header.Extra) // As mentioned above, we pre-verify the extra data to ensure this never happens. diff --git a/miner/worker.go b/miner/worker.go index 8bfa535ee7..9d83be0020 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -352,8 +352,6 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP // commit runs any post-transaction state modifications, assembles the final block // and commits new work if consensus engine is running. func (w *worker) commit(env *environment) (*types.Block, error) { - // Deep copy receipts here to avoid interaction between different tasks. - receipts := copyReceipts(env.receipts) if env.rules.IsDUpgrade { predicateResultsBytes, err := env.predicateResults.Bytes() if err != nil { @@ -361,6 +359,8 @@ func (w *worker) commit(env *environment) (*types.Block, error) { } env.header.Extra = append(env.header.Extra, predicateResultsBytes...) } + // Deep copy receipts here to avoid interaction between different tasks. + receipts := copyReceipts(env.receipts) block, err := w.engine.FinalizeAndAssemble(w.chain, env.header, env.parent, env.state, env.txs, nil, receipts) if err != nil { return nil, err diff --git a/plugin/evm/block.go b/plugin/evm/block.go index ae126790e5..a16bc894ed 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -19,6 +19,7 @@ import ( "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ava-labs/subnet-evm/precompile/results" + "github.com/ava-labs/subnet-evm/utils/predicate" "github.com/ava-labs/subnet-evm/warp/payload" "github.com/ava-labs/subnet-evm/x/warp" @@ -289,10 +290,10 @@ func (b *Block) verifyPredicates(predicateContext *precompileconfig.PredicateCon return fmt.Errorf("failed to marshal predicate results: %w", err) } extraData := b.ethBlock.Extra() - if len(extraData) < params.DynamicFeeExtraDataSize { - return fmt.Errorf("header extra data too short for predicate verification found: %d, required: %d", len(extraData), params.DynamicFeeExtraDataSize) + headerPredicateResultsBytes, err := predicate.GetPredicateResultBytes(extraData) + if err != nil { + return err } - headerPredicateResultsBytes := extraData[params.DynamicFeeExtraDataSize:] if !bytes.Equal(headerPredicateResultsBytes, predicateResultsBytes) { return fmt.Errorf("%w (remote: %x local: %x)", errInvalidHeaderPredicateResults, headerPredicateResultsBytes, predicateResultsBytes) } diff --git a/utils/predicate/predicate_bytes.go b/utils/predicate/predicate_bytes.go index 608b503f12..2e1b3690e8 100644 --- a/utils/predicate/predicate_bytes.go +++ b/utils/predicate/predicate_bytes.go @@ -6,6 +6,7 @@ package predicate import ( "fmt" + "github.com/ava-labs/subnet-evm/params" "github.com/ethereum/go-ethereum/common" ) @@ -16,6 +17,13 @@ import ( // append/remove padding. var PredicateEndByte = byte(0xff) +var ( + ErrInvalidAllZeroBytes = fmt.Errorf("predicate specified invalid all zero bytes") + ErrInvalidPadding = fmt.Errorf("predicate specified invalid padding") + ErrInvalidEndDelimiter = fmt.Errorf("invalid end delimiter") + ErrorInvalidExtraData = fmt.Errorf("header extra data too short for predicate verification") +) + // PackPredicate packs [predicate] by delimiting the actual message with [PredicateEndByte] // and zero padding to reach a length that is a multiple of 32. func PackPredicate(predicate []byte) []byte { @@ -29,15 +37,15 @@ func PackPredicate(predicate []byte) []byte { func UnpackPredicate(paddedPredicate []byte) ([]byte, error) { trimmedPredicateBytes := common.TrimRightZeroes(paddedPredicate) if len(trimmedPredicateBytes) == 0 { - return nil, fmt.Errorf("predicate specified invalid all zero bytes: 0x%x", paddedPredicate) + return nil, fmt.Errorf("%w: 0x%x", ErrInvalidAllZeroBytes, paddedPredicate) } if expectedPaddedLength := (len(trimmedPredicateBytes) + 31) / 32 * 32; expectedPaddedLength != len(paddedPredicate) { - return nil, fmt.Errorf("predicate specified invalid padding with length (%d), expected length (%d)", len(paddedPredicate), expectedPaddedLength) + return nil, fmt.Errorf("%w: got length (%d), expected length (%d)", ErrInvalidPadding, len(paddedPredicate), expectedPaddedLength) } if trimmedPredicateBytes[len(trimmedPredicateBytes)-1] != PredicateEndByte { - return nil, fmt.Errorf("invalid end delimiter") + return nil, ErrInvalidEndDelimiter } return trimmedPredicateBytes[:len(trimmedPredicateBytes)-1], nil @@ -66,3 +74,15 @@ func BytesToHashSlice(b []byte) []common.Hash { } return hashes } + +// GetPredicateResultBytes returns the predicate result bytes from the extra data. +// If the extra data does not contain predicate result bytes, an error is returned. +func GetPredicateResultBytes(extraData []byte) ([]byte, error) { + // Prior to the DUpgrade, the VM enforces the extra data is smaller than or equal to this size. + // After the DUpgrade, the VM pre-verifies the extra data past the dynamic fee rollup window is + // valid. + if len(extraData) < params.DynamicFeeExtraDataSize { + return nil, fmt.Errorf("%w: got: %d, required: %d", ErrorInvalidExtraData, len(extraData), params.DynamicFeeExtraDataSize) + } + return extraData[params.DynamicFeeExtraDataSize:], nil +} From b8c0dfddc2e73255502989a01171ecb7c2114baa Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Thu, 28 Sep 2023 12:10:25 -0400 Subject: [PATCH 19/52] clear messageCache on backend.Clear (#918) * clear messageCache on backend.Clear * update test --- warp/backend.go | 1 + warp/backend_test.go | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/warp/backend.go b/warp/backend.go index 92faa0d409..fac81248c1 100644 --- a/warp/backend.go +++ b/warp/backend.go @@ -55,6 +55,7 @@ func NewBackend(warpSigner avalancheWarp.Signer, db database.Database, cacheSize func (b *backend) Clear() error { b.signatureCache.Flush() + b.messageCache.Flush() return database.Clear(b.db, batchSize) } diff --git a/warp/backend_test.go b/warp/backend_test.go index 3cedcc5552..9f92234b44 100644 --- a/warp/backend_test.go +++ b/warp/backend_test.go @@ -26,7 +26,9 @@ func TestClearDB(t *testing.T) { sk, err := bls.NewSecretKey() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID) - backend := NewBackend(warpSigner, db, 500) + backendIntf := NewBackend(warpSigner, db, 500) + backend, ok := backendIntf.(*backend) + require.True(t, ok) // use multiple messages to test that all messages get cleared payloads := [][]byte{[]byte("test1"), []byte("test2"), []byte("test3"), []byte("test4"), []byte("test5")} @@ -47,6 +49,11 @@ func TestClearDB(t *testing.T) { err = backend.Clear() require.NoError(t, err) + require.Zero(t, backend.messageCache.Len()) + require.Zero(t, backend.signatureCache.Len()) + it := db.NewIterator() + defer it.Release() + require.False(t, it.Next()) // ensure all messages have been deleted for _, messageID := range messageIDs { From 40d1217191ee58e4f2e48172069eb8ba225e145f Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Fri, 29 Sep 2023 08:09:54 -0400 Subject: [PATCH 20/52] Signature Aggregation Refactor (#883) * remove unused code * remove signature aggregation job * appease linter * remove signature aggregation job * move all signature aggregeation logic to AggregateSignatures * nit * nit * nit * add mock and tests * clean up tests * nit * don't autogen SignatureGetter because it requires manual changes because gomock is broken * nit * comment * comments * lower log level * re-add todo * nit * refactor aggregeateSignatures * comment nits * add tests * use error from avalancheWap; combine functions; nits * add logs * typo fix --- plugin/evm/vm.go | 2 +- warp/aggregator/aggregation_job.go | 170 -------- warp/aggregator/aggregation_job_test.go | 355 ---------------- warp/aggregator/aggregator.go | 188 +++++++- warp/aggregator/aggregator_test.go | 425 +++++++++++++++++++ warp/aggregator/mock_signature_getter.go | 53 +++ warp/aggregator/network_signature_backend.go | 6 +- warp/aggregator/signature_job.go | 60 --- warp/aggregator/signature_job_test.go | 140 ------ 9 files changed, 654 insertions(+), 745 deletions(-) delete mode 100644 warp/aggregator/aggregation_job.go delete mode 100644 warp/aggregator/aggregation_job_test.go create mode 100644 warp/aggregator/aggregator_test.go create mode 100644 warp/aggregator/mock_signature_getter.go delete mode 100644 warp/aggregator/signature_job.go delete mode 100644 warp/aggregator/signature_job_test.go diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 304551988d..a6ebed2339 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -943,7 +943,7 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]*commonEng.HTTPHandler } if vm.config.WarpAPIEnabled { - warpAggregator := aggregator.NewAggregator(vm.ctx.SubnetID, warpValidators.NewState(vm.ctx), &aggregator.NetworkSigner{Client: vm.client}) + warpAggregator := aggregator.New(vm.ctx.SubnetID, warpValidators.NewState(vm.ctx), &aggregator.NetworkSigner{Client: vm.client}) if err := handler.RegisterName("warp", warp.NewAPI(vm.warpBackend, warpAggregator)); err != nil { return nil, err } diff --git a/warp/aggregator/aggregation_job.go b/warp/aggregator/aggregation_job.go deleted file mode 100644 index 70c18c72ff..0000000000 --- a/warp/aggregator/aggregation_job.go +++ /dev/null @@ -1,170 +0,0 @@ -// (c) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package aggregator - -import ( - "context" - "fmt" - "sync" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow/validators" - "github.com/ava-labs/avalanchego/utils/crypto/bls" - "github.com/ava-labs/avalanchego/utils/set" - avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/log" -) - -// signatureAggregationJob fetches signatures for a single unsigned warp message. -type signatureAggregationJob struct { - // SignatureBackend is assumed to be thread-safe and may be used by multiple signature aggregation jobs concurrently - client SignatureBackend - height uint64 - subnetID ids.ID - - // Minimum threshold at which to return the resulting aggregate signature. If this threshold is not reached, - // return an error instead of aggregating the signatures that were fetched. - minValidQuorumNum uint64 - // Threshold at which to cancel fetching further signatures - maxNeededQuorumNum uint64 - // Denominator to use when checking if we've reached the threshold - quorumDen uint64 - state validators.State - msg *avalancheWarp.UnsignedMessage -} - -type AggregateSignatureResult struct { - SignatureWeight uint64 - TotalWeight uint64 - Message *avalancheWarp.Message -} - -func newSignatureAggregationJob( - client SignatureBackend, - height uint64, - subnetID ids.ID, - minValidQuorumNum uint64, - maxNeededQuorumNum uint64, - quorumDen uint64, - state validators.State, - msg *avalancheWarp.UnsignedMessage, -) *signatureAggregationJob { - return &signatureAggregationJob{ - client: client, - height: height, - subnetID: subnetID, - minValidQuorumNum: minValidQuorumNum, - maxNeededQuorumNum: maxNeededQuorumNum, - quorumDen: quorumDen, - state: state, - msg: msg, - } -} - -// Execute aggregates signatures for the requested message -func (a *signatureAggregationJob) Execute(ctx context.Context) (*AggregateSignatureResult, error) { - log.Info("Fetching signature", "subnetID", a.subnetID, "height", a.height) - validators, totalWeight, err := avalancheWarp.GetCanonicalValidatorSet(ctx, a.state, a.height, a.subnetID) - if err != nil { - return nil, fmt.Errorf("failed to get validator set: %w", err) - } - if len(validators) == 0 { - return nil, fmt.Errorf("cannot aggregate signatures from subnet with no validators (SubnetID: %s, Height: %d)", a.subnetID, a.height) - } - - signatureJobs := make([]*signatureJob, len(validators)) - for i, validator := range validators { - signatureJobs[i] = newSignatureJob(a.client, validator, a.msg) - } - - var ( - // [signatureLock] must be held when accessing [blsSignatures], [bitSet], or [signatureWeight] - // in the goroutine below. - signatureLock sync.Mutex - blsSignatures = make([]*bls.Signature, 0, len(signatureJobs)) - bitSet = set.NewBits() - signatureWeight = uint64(0) - ) - - // Create a child context to cancel signature fetching if we reach [maxNeededQuorumNum] threshold - signatureFetchCtx, signatureFetchCancel := context.WithCancel(ctx) - defer signatureFetchCancel() - - wg := sync.WaitGroup{} - wg.Add(len(signatureJobs)) - for i, signatureJob := range signatureJobs { - i := i - signatureJob := signatureJob - go func() { - defer wg.Done() - - log.Info("Fetching warp signature", - "nodeID", signatureJob.nodeID, - "index", i, - ) - - blsSignature, err := signatureJob.Execute(signatureFetchCtx) - if err != nil { - log.Info("Failed to fetch signature at index %d: %s", i, signatureJob) - return - } - log.Info("Retrieved warp signature", - "nodeID", signatureJob.nodeID, - "index", i, - "signature", hexutil.Bytes(bls.SignatureToBytes(blsSignature)), - ) - - // Add the signature and check if we've reached the requested threshold - signatureLock.Lock() - defer signatureLock.Unlock() - - blsSignatures = append(blsSignatures, blsSignature) - bitSet.Add(i) - signatureWeight += signatureJob.weight - log.Info("Updated weight", - "totalWeight", signatureWeight, - "addedWeight", signatureJob.weight, - ) - - // If the signature weight meets the requested threshold, cancel signature fetching - if err := avalancheWarp.VerifyWeight(signatureWeight, totalWeight, a.maxNeededQuorumNum, a.quorumDen); err == nil { - log.Info("Verify weight passed, exiting aggregation early", - "maxNeededQuorumNum", a.maxNeededQuorumNum, - "totalWeight", totalWeight, - "signatureWeight", signatureWeight, - ) - signatureFetchCancel() - } - }() - } - wg.Wait() - - // If I failed to fetch sufficient signature stake, return an error - if err := avalancheWarp.VerifyWeight(signatureWeight, totalWeight, a.minValidQuorumNum, a.quorumDen); err != nil { - return nil, fmt.Errorf("failed to aggregate signature: %w", err) - } - - // Otherwise, return the aggregate signature - aggregateSignature, err := bls.AggregateSignatures(blsSignatures) - if err != nil { - return nil, fmt.Errorf("failed to aggregate BLS signatures: %w", err) - } - - warpSignature := &avalancheWarp.BitSetSignature{ - Signers: bitSet.Bytes(), - } - copy(warpSignature.Signature[:], bls.SignatureToBytes(aggregateSignature)) - - msg, err := avalancheWarp.NewMessage(a.msg, warpSignature) - if err != nil { - return nil, fmt.Errorf("failed to construct warp message: %w", err) - } - - return &AggregateSignatureResult{ - Message: msg, - SignatureWeight: signatureWeight, - TotalWeight: totalWeight, - }, nil -} diff --git a/warp/aggregator/aggregation_job_test.go b/warp/aggregator/aggregation_job_test.go deleted file mode 100644 index a184138489..0000000000 --- a/warp/aggregator/aggregation_job_test.go +++ /dev/null @@ -1,355 +0,0 @@ -// (c) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package aggregator - -import ( - "context" - "errors" - "testing" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow/validators" - "github.com/ava-labs/avalanchego/utils/crypto/bls" - "github.com/ava-labs/avalanchego/utils/set" - avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" - "github.com/stretchr/testify/require" -) - -var ( - subnetID = ids.GenerateTestID() - pChainHeight = uint64(10) - getSubnetIDF = func(ctx context.Context, chainID ids.ID) (ids.ID, error) { return subnetID, nil } - getCurrentHeightF = func(ctx context.Context) (uint64, error) { return pChainHeight, nil } -) - -type signatureAggregationTest struct { - ctx context.Context - job *signatureAggregationJob - expectedRes *AggregateSignatureResult - expectedErr error -} - -func executeSignatureAggregationTest(t testing.TB, test signatureAggregationTest) { - t.Helper() - - res, err := test.job.Execute(test.ctx) - if test.expectedErr != nil { - require.ErrorIs(t, err, test.expectedErr) - return - } - - require.Equal(t, res.SignatureWeight, test.expectedRes.SignatureWeight) - require.Equal(t, res.TotalWeight, test.expectedRes.TotalWeight) - require.NoError(t, res.Message.Signature.Verify( - context.Background(), - &res.Message.UnsignedMessage, - networkID, - test.job.state, - pChainHeight, - test.job.minValidQuorumNum, - test.job.quorumDen, - )) -} - -func TestSingleSignatureAggregator(t *testing.T) { - ctx := context.Background() - aggregationJob := newSignatureAggregationJob( - &mockFetcher{ - fetch: func(context.Context, ids.NodeID, *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { - return blsSignatures[0], nil - }, - }, - pChainHeight, - subnetID, - 100, - 100, - 100, - &validators.TestState{ - GetSubnetIDF: getSubnetIDF, - GetCurrentHeightF: getCurrentHeightF, - GetValidatorSetF: func(ctx context.Context, height uint64, subnetID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - return map[ids.NodeID]*validators.GetValidatorOutput{ - nodeIDs[0]: { - NodeID: nodeIDs[0], - PublicKey: blsPublicKeys[0], - Weight: 100, - }, - }, nil - }, - }, - unsignedMsg, - ) - - signature := &avalancheWarp.BitSetSignature{ - Signers: set.NewBits(0).Bytes(), - } - signedMessage, err := avalancheWarp.NewMessage(unsignedMsg, signature) - require.NoError(t, err) - copy(signature.Signature[:], bls.SignatureToBytes(blsSignatures[0])) - expectedRes := &AggregateSignatureResult{ - SignatureWeight: 100, - TotalWeight: 100, - Message: signedMessage, - } - executeSignatureAggregationTest(t, signatureAggregationTest{ - ctx: ctx, - job: aggregationJob, - expectedRes: expectedRes, - }) -} - -func TestAggregateAllSignatures(t *testing.T) { - ctx := context.Background() - aggregationJob := newSignatureAggregationJob( - &mockFetcher{ - fetch: func(_ context.Context, nodeID ids.NodeID, _ *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { - for i, matchingNodeID := range nodeIDs { - if matchingNodeID == nodeID { - return blsSignatures[i], nil - } - } - panic("request to unexpected nodeID") - }, - }, - pChainHeight, - subnetID, - 100, - 100, - 100, - &validators.TestState{ - GetSubnetIDF: getSubnetIDF, - GetCurrentHeightF: getCurrentHeightF, - GetValidatorSetF: func(ctx context.Context, height uint64, subnetID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - res := make(map[ids.NodeID]*validators.GetValidatorOutput) - for i := 0; i < 5; i++ { - res[nodeIDs[i]] = &validators.GetValidatorOutput{ - NodeID: nodeIDs[i], - PublicKey: blsPublicKeys[i], - Weight: 100, - } - } - return res, nil - }, - }, - unsignedMsg, - ) - - signature := &avalancheWarp.BitSetSignature{ - Signers: set.NewBits(0, 1, 2, 3, 4).Bytes(), - } - signedMessage, err := avalancheWarp.NewMessage(unsignedMsg, signature) - require.NoError(t, err) - aggregateSignature, err := bls.AggregateSignatures(blsSignatures) - require.NoError(t, err) - copy(signature.Signature[:], bls.SignatureToBytes(aggregateSignature)) - expectedRes := &AggregateSignatureResult{ - SignatureWeight: 500, - TotalWeight: 500, - Message: signedMessage, - } - executeSignatureAggregationTest(t, signatureAggregationTest{ - ctx: ctx, - job: aggregationJob, - expectedRes: expectedRes, - }) -} - -func TestAggregateThresholdSignatures(t *testing.T) { - ctx := context.Background() - aggregationJob := newSignatureAggregationJob( - &mockFetcher{ - fetch: func(_ context.Context, nodeID ids.NodeID, _ *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { - for i, matchingNodeID := range nodeIDs[:3] { - if matchingNodeID == nodeID { - return blsSignatures[i], nil - } - } - return nil, errors.New("what do we say to the god of death") - }, - }, - pChainHeight, - subnetID, - 60, - 60, - 100, - &validators.TestState{ - GetSubnetIDF: getSubnetIDF, - GetCurrentHeightF: getCurrentHeightF, - GetValidatorSetF: func(ctx context.Context, height uint64, subnetID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - res := make(map[ids.NodeID]*validators.GetValidatorOutput) - for i := 0; i < 5; i++ { - res[nodeIDs[i]] = &validators.GetValidatorOutput{ - NodeID: nodeIDs[i], - PublicKey: blsPublicKeys[i], - Weight: 100, - } - } - return res, nil - }, - }, - unsignedMsg, - ) - - signature := &avalancheWarp.BitSetSignature{ - Signers: set.NewBits(0, 1, 2).Bytes(), - } - signedMessage, err := avalancheWarp.NewMessage(unsignedMsg, signature) - require.NoError(t, err) - aggregateSignature, err := bls.AggregateSignatures(blsSignatures) - require.NoError(t, err) - copy(signature.Signature[:], bls.SignatureToBytes(aggregateSignature)) - expectedRes := &AggregateSignatureResult{ - SignatureWeight: 300, - TotalWeight: 500, - Message: signedMessage, - } - executeSignatureAggregationTest(t, signatureAggregationTest{ - ctx: ctx, - job: aggregationJob, - expectedRes: expectedRes, - }) -} - -func TestAggregateThresholdSignaturesInsufficientWeight(t *testing.T) { - ctx := context.Background() - aggregationJob := newSignatureAggregationJob( - &mockFetcher{ - fetch: func(_ context.Context, nodeID ids.NodeID, _ *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { - for i, matchingNodeID := range nodeIDs[:3] { - if matchingNodeID == nodeID { - return blsSignatures[i], nil - } - } - return nil, errors.New("what do we say to the god of death") - }, - }, - pChainHeight, - subnetID, - 80, - 80, - 100, - &validators.TestState{ - GetSubnetIDF: getSubnetIDF, - GetCurrentHeightF: getCurrentHeightF, - GetValidatorSetF: func(ctx context.Context, height uint64, subnetID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - res := make(map[ids.NodeID]*validators.GetValidatorOutput) - for i := 0; i < 5; i++ { - res[nodeIDs[i]] = &validators.GetValidatorOutput{ - NodeID: nodeIDs[i], - PublicKey: blsPublicKeys[i], - Weight: 100, - } - } - return res, nil - }, - }, - unsignedMsg, - ) - - executeSignatureAggregationTest(t, signatureAggregationTest{ - ctx: ctx, - job: aggregationJob, - expectedErr: avalancheWarp.ErrInsufficientWeight, - }) -} - -func TestAggregateThresholdSignaturesBlockingRequests(t *testing.T) { - ctx := context.Background() - aggregationJob := newSignatureAggregationJob( - &mockFetcher{ - fetch: func(ctx context.Context, nodeID ids.NodeID, _ *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { - for i, matchingNodeID := range nodeIDs[:3] { - if matchingNodeID == nodeID { - return blsSignatures[i], nil - } - } - - // Block until the context is cancelled and return the error if not available - <-ctx.Done() - return nil, ctx.Err() - }, - }, - pChainHeight, - subnetID, - 60, - 60, - 100, - &validators.TestState{ - GetSubnetIDF: getSubnetIDF, - GetCurrentHeightF: getCurrentHeightF, - GetValidatorSetF: func(ctx context.Context, height uint64, subnetID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - res := make(map[ids.NodeID]*validators.GetValidatorOutput) - for i := 0; i < 5; i++ { - res[nodeIDs[i]] = &validators.GetValidatorOutput{ - NodeID: nodeIDs[i], - PublicKey: blsPublicKeys[i], - Weight: 100, - } - } - return res, nil - }, - }, - unsignedMsg, - ) - - signature := &avalancheWarp.BitSetSignature{ - Signers: set.NewBits(0, 1, 2).Bytes(), - } - signedMessage, err := avalancheWarp.NewMessage(unsignedMsg, signature) - require.NoError(t, err) - aggregateSignature, err := bls.AggregateSignatures(blsSignatures) - require.NoError(t, err) - copy(signature.Signature[:], bls.SignatureToBytes(aggregateSignature)) - expectedRes := &AggregateSignatureResult{ - SignatureWeight: 300, - TotalWeight: 500, - Message: signedMessage, - } - executeSignatureAggregationTest(t, signatureAggregationTest{ - ctx: ctx, - job: aggregationJob, - expectedRes: expectedRes, - }) -} - -func TestAggregateThresholdSignaturesParentCtxCancels(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - cancel() - aggregationJob := newSignatureAggregationJob( - &mockFetcher{ - fetch: func(ctx context.Context, nodeID ids.NodeID, _ *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { - // Block until the context is cancelled and return the error if not available - <-ctx.Done() - return nil, ctx.Err() - }, - }, - pChainHeight, - subnetID, - 60, - 60, - 100, - &validators.TestState{ - GetSubnetIDF: getSubnetIDF, - GetCurrentHeightF: getCurrentHeightF, - GetValidatorSetF: func(ctx context.Context, height uint64, subnetID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - res := make(map[ids.NodeID]*validators.GetValidatorOutput) - for i := 0; i < 5; i++ { - res[nodeIDs[i]] = &validators.GetValidatorOutput{ - NodeID: nodeIDs[i], - PublicKey: blsPublicKeys[i], - Weight: 100, - } - } - return res, nil - }, - }, - unsignedMsg, - ) - - executeSignatureAggregationTest(t, signatureAggregationTest{ - ctx: ctx, - job: aggregationJob, - expectedErr: avalancheWarp.ErrInsufficientWeight, - }) -} diff --git a/warp/aggregator/aggregator.go b/warp/aggregator/aggregator.go index 3530d4a700..8633205373 100644 --- a/warp/aggregator/aggregator.go +++ b/warp/aggregator/aggregator.go @@ -5,22 +5,51 @@ package aggregator import ( "context" + "errors" + "fmt" + + "github.com/ava-labs/subnet-evm/params" + + "github.com/ethereum/go-ethereum/log" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/set" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" - "github.com/ava-labs/subnet-evm/params" ) -// Aggregator fulfills requests to aggregate signatures of a subnet's validator set for Avalanche Warp Messages. +var errNoValidators = errors.New("cannot aggregate signatures from subnet with no validators") + +// SignatureGetter defines the minimum network interface to perform signature aggregation +type SignatureGetter interface { + // GetSignature attempts to fetch a BLS Signature from [nodeID] for [unsignedWarpMessage] + GetSignature(ctx context.Context, nodeID ids.NodeID, unsignedWarpMessage *avalancheWarp.UnsignedMessage) (*bls.Signature, error) +} + +type AggregateSignatureResult struct { + // Weight of validators included in the aggregate signature. + SignatureWeight uint64 + // Total weight of all validators in the subnet. + TotalWeight uint64 + // The message with the aggregate signature. + Message *avalancheWarp.Message +} + +// Aggregator requests signatures from validators and +// aggregates them into a single signature. type Aggregator struct { + // Aggregating signatures for a chain validated by this subnet. subnetID ids.ID - client SignatureBackend - state validators.State + // Fetches signatures from validators. + client SignatureGetter + // Validator state for this chain. + state validators.State } -// NewAggregator returns a signature aggregator, which will aggregate Warp Signatures for the given [ -func NewAggregator(subnetID ids.ID, state validators.State, client SignatureBackend) *Aggregator { +// New returns a signature aggregator for the chain with the given [state] on the +// given [subnetID], and where [client] can be used to fetch signatures from validators. +func New(subnetID ids.ID, state validators.State, client SignatureGetter) *Aggregator { return &Aggregator{ subnetID: subnetID, client: client, @@ -28,6 +57,8 @@ func NewAggregator(subnetID ids.ID, state validators.State, client SignatureBack } } +// Returns an aggregate signature over [unsignedMessage]. +// The returned signature's weight exceeds the threshold given by [quorumNum]. func (a *Aggregator) AggregateSignatures(ctx context.Context, unsignedMessage *avalancheWarp.UnsignedMessage, quorumNum uint64) (*AggregateSignatureResult, error) { // Note: we use the current height as a best guess of the canonical validator set when the aggregated signature will be verified // by the recipient chain. If the validator set changes from [pChainHeight] to the P-Chain height that is actually specified by the @@ -36,16 +67,141 @@ func (a *Aggregator) AggregateSignatures(ctx context.Context, unsignedMessage *a if err != nil { return nil, err } - job := newSignatureAggregationJob( - a.client, - pChainHeight, - a.subnetID, - quorumNum, - quorumNum, - params.WarpQuorumDenominator, - a.state, - unsignedMessage, + + log.Debug("Fetching signature", + "a.subnetID", a.subnetID, + "height", pChainHeight, + ) + validators, totalWeight, err := avalancheWarp.GetCanonicalValidatorSet(ctx, a.state, pChainHeight, a.subnetID) + if err != nil { + return nil, fmt.Errorf("failed to get validator set: %w", err) + } + if len(validators) == 0 { + return nil, fmt.Errorf("%w (SubnetID: %s, Height: %d)", errNoValidators, a.subnetID, pChainHeight) + } + + type signatureFetchResult struct { + sig *bls.Signature + index int + weight uint64 + } + + // Create a child context to cancel signature fetching if we reach signature threshold. + signatureFetchCtx, signatureFetchCancel := context.WithCancel(ctx) + defer signatureFetchCancel() + + // Fetch signatures from validators concurrently. + signatureFetchResultChan := make(chan *signatureFetchResult) + for i, validator := range validators { + var ( + i = i + validator = validator + // TODO: update from a single nodeID to the original slice and use extra nodeIDs as backup. + nodeID = validator.NodeIDs[0] + ) + go func() { + log.Debug("Fetching warp signature", + "nodeID", nodeID, + "index", i, + "msgID", unsignedMessage.ID(), + ) + + signature, err := a.client.GetSignature(signatureFetchCtx, nodeID, unsignedMessage) + if err != nil { + log.Debug("Failed to fetch warp signature", + "nodeID", nodeID, + "index", i, + "err", err, + "msgID", unsignedMessage.ID(), + ) + signatureFetchResultChan <- nil + return + } + + log.Debug("Retrieved warp signature", + "nodeID", nodeID, + "msgID", unsignedMessage.ID(), + "index", i, + ) + + if !bls.Verify(validator.PublicKey, signature, unsignedMessage.Bytes()) { + log.Debug("Failed to verify warp signature", + "nodeID", nodeID, + "index", i, + "msgID", unsignedMessage.ID(), + ) + signatureFetchResultChan <- nil + return + } + + signatureFetchResultChan <- &signatureFetchResult{ + sig: signature, + index: i, + weight: validator.Weight, + } + }() + } + + var ( + signatures = make([]*bls.Signature, 0, len(validators)) + signersBitset = set.NewBits() + signaturesWeight = uint64(0) + signaturesPassedThreshold = false ) - return job.Execute(ctx) + for i := 0; i < len(validators); i++ { + signatureFetchResult := <-signatureFetchResultChan + if signatureFetchResult == nil { + continue + } + + signatures = append(signatures, signatureFetchResult.sig) + signersBitset.Add(signatureFetchResult.index) + signaturesWeight += signatureFetchResult.weight + log.Debug("Updated weight", + "totalWeight", signaturesWeight, + "addedWeight", signatureFetchResult.weight, + "msgID", unsignedMessage.ID(), + ) + + // If the signature weight meets the requested threshold, cancel signature fetching + if err := avalancheWarp.VerifyWeight(signaturesWeight, totalWeight, quorumNum, params.WarpQuorumDenominator); err == nil { + log.Debug("Verify weight passed, exiting aggregation early", + "quorumNum", quorumNum, + "totalWeight", totalWeight, + "signatureWeight", signaturesWeight, + "msgID", unsignedMessage.ID(), + ) + signatureFetchCancel() + signaturesPassedThreshold = true + break + } + } + + // If I failed to fetch sufficient signature stake, return an error + if !signaturesPassedThreshold { + return nil, avalancheWarp.ErrInsufficientWeight + } + + // Otherwise, return the aggregate signature + aggregateSignature, err := bls.AggregateSignatures(signatures) + if err != nil { + return nil, fmt.Errorf("failed to aggregate BLS signatures: %w", err) + } + + warpSignature := &avalancheWarp.BitSetSignature{ + Signers: signersBitset.Bytes(), + } + copy(warpSignature.Signature[:], bls.SignatureToBytes(aggregateSignature)) + + msg, err := avalancheWarp.NewMessage(unsignedMessage, warpSignature) + if err != nil { + return nil, fmt.Errorf("failed to construct warp message: %w", err) + } + + return &AggregateSignatureResult{ + Message: msg, + SignatureWeight: signaturesWeight, + TotalWeight: totalWeight, + }, nil } diff --git a/warp/aggregator/aggregator_test.go b/warp/aggregator/aggregator_test.go new file mode 100644 index 0000000000..01225f89c1 --- /dev/null +++ b/warp/aggregator/aggregator_test.go @@ -0,0 +1,425 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package aggregator + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "go.uber.org/mock/gomock" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" +) + +func newValidator(t testing.TB, weight uint64) (*bls.SecretKey, *avalancheWarp.Validator) { + sk, err := bls.NewSecretKey() + require.NoError(t, err) + pk := bls.PublicFromSecretKey(sk) + return sk, &avalancheWarp.Validator{ + PublicKey: pk, + PublicKeyBytes: bls.PublicKeyToBytes(pk), + Weight: weight, + NodeIDs: []ids.NodeID{ids.GenerateTestNodeID()}, + } +} + +func TestAggregateSignatures(t *testing.T) { + subnetID := ids.GenerateTestID() + errTest := errors.New("test error") + pChainHeight := uint64(1337) + unsignedMsg := &avalancheWarp.UnsignedMessage{ + NetworkID: 1338, + SourceChainID: ids.ID{'y', 'e', 'e', 't'}, + Payload: []byte("hello world"), + } + require.NoError(t, unsignedMsg.Initialize()) + + nodeID1, nodeID2, nodeID3 := ids.GenerateTestNodeID(), ids.GenerateTestNodeID(), ids.GenerateTestNodeID() + vdrWeight := uint64(10001) + vdr1sk, vdr1 := newValidator(t, vdrWeight) + vdr2sk, vdr2 := newValidator(t, vdrWeight+1) + vdr3sk, vdr3 := newValidator(t, vdrWeight-1) + sig1 := bls.Sign(vdr1sk, unsignedMsg.Bytes()) + sig2 := bls.Sign(vdr2sk, unsignedMsg.Bytes()) + sig3 := bls.Sign(vdr3sk, unsignedMsg.Bytes()) + vdrToSig := map[*avalancheWarp.Validator]*bls.Signature{ + vdr1: sig1, + vdr2: sig2, + vdr3: sig3, + } + nonVdrSk, err := bls.NewSecretKey() + require.NoError(t, err) + nonVdrSig := bls.Sign(nonVdrSk, unsignedMsg.Bytes()) + vdrSet := map[ids.NodeID]*validators.GetValidatorOutput{ + nodeID1: { + NodeID: nodeID1, + PublicKey: vdr1.PublicKey, + Weight: vdr1.Weight, + }, + nodeID2: { + NodeID: nodeID2, + PublicKey: vdr2.PublicKey, + Weight: vdr2.Weight, + }, + nodeID3: { + NodeID: nodeID3, + PublicKey: vdr3.PublicKey, + Weight: vdr3.Weight, + }, + } + + type test struct { + name string + contextFunc func() context.Context + aggregatorFunc func(*gomock.Controller) *Aggregator + unsignedMsg *avalancheWarp.UnsignedMessage + quorumNum uint64 + expectedSigners []*avalancheWarp.Validator + expectedErr error + } + + tests := []test{ + { + name: "can't get height", + contextFunc: context.Background, + aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + state := validators.NewMockState(ctrl) + state.EXPECT().GetCurrentHeight(gomock.Any()).Return(uint64(0), errTest) + return New(subnetID, state, nil) + }, + unsignedMsg: nil, + quorumNum: 0, + expectedErr: errTest, + }, + { + name: "can't get validator set", + contextFunc: context.Background, + aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + state := validators.NewMockState(ctrl) + state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) + state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errTest) + return New(subnetID, state, nil) + }, + unsignedMsg: nil, + expectedErr: errTest, + }, + { + name: "no validators exist", + contextFunc: context.Background, + aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + state := validators.NewMockState(ctrl) + state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) + state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil) + return New(subnetID, state, nil) + }, + unsignedMsg: nil, + quorumNum: 0, + expectedErr: errNoValidators, + }, + { + name: "0/3 validators reply with signature", + contextFunc: context.Background, + aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + state := validators.NewMockState(ctrl) + state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) + state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( + vdrSet, nil, + ) + + client := NewMockSignatureGetter(ctrl) + client.EXPECT().GetSignature(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errTest).AnyTimes() + return New(subnetID, state, client) + }, + unsignedMsg: unsignedMsg, + quorumNum: 1, + expectedErr: avalancheWarp.ErrInsufficientWeight, + }, + { + name: "1/3 validators reply with signature; insufficient weight", + contextFunc: context.Background, + aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + state := validators.NewMockState(ctrl) + state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) + state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( + vdrSet, nil, + ) + + client := NewMockSignatureGetter(ctrl) + client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(sig1, nil) + client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(nil, errTest) + client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(nil, errTest) + return New(subnetID, state, client) + }, + unsignedMsg: unsignedMsg, + quorumNum: 35, // Require >1/3 of weight + expectedErr: avalancheWarp.ErrInsufficientWeight, + }, + { + name: "2/3 validators reply with signature; insufficient weight", + contextFunc: context.Background, + aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + state := validators.NewMockState(ctrl) + state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) + state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( + vdrSet, nil, + ) + + client := NewMockSignatureGetter(ctrl) + client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(sig1, nil) + client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(sig2, nil) + client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(nil, errTest) + return New(subnetID, state, client) + }, + unsignedMsg: unsignedMsg, + quorumNum: 69, // Require >2/3 of weight + expectedErr: avalancheWarp.ErrInsufficientWeight, + }, + { + name: "2/3 validators reply with signature; sufficient weight", + contextFunc: context.Background, + aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + state := validators.NewMockState(ctrl) + state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) + state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( + vdrSet, nil, + ) + + client := NewMockSignatureGetter(ctrl) + client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(sig1, nil) + client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(sig2, nil) + client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(nil, errTest) + return New(subnetID, state, client) + }, + unsignedMsg: unsignedMsg, + quorumNum: 65, // Require <2/3 of weight + expectedSigners: []*avalancheWarp.Validator{vdr1, vdr2}, + expectedErr: nil, + }, + { + name: "3/3 validators reply with signature; sufficient weight", + contextFunc: context.Background, + aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + state := validators.NewMockState(ctrl) + state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) + state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( + vdrSet, nil, + ) + + client := NewMockSignatureGetter(ctrl) + client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(sig1, nil) + client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(sig2, nil) + client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(sig3, nil) + return New(subnetID, state, client) + }, + unsignedMsg: unsignedMsg, + quorumNum: 100, // Require all weight + expectedSigners: []*avalancheWarp.Validator{vdr1, vdr2, vdr3}, + expectedErr: nil, + }, + { + name: "3/3 validators reply with signature; 1 invalid signature; sufficient weight", + contextFunc: context.Background, + aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + state := validators.NewMockState(ctrl) + state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) + state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( + vdrSet, nil, + ) + + client := NewMockSignatureGetter(ctrl) + client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(nonVdrSig, nil) + client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(sig2, nil) + client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(sig3, nil) + return New(subnetID, state, client) + }, + unsignedMsg: unsignedMsg, + quorumNum: 64, + expectedSigners: []*avalancheWarp.Validator{vdr2, vdr3}, + expectedErr: nil, + }, + { + name: "3/3 validators reply with signature; 3 invalid signatures; insufficient weight", + contextFunc: context.Background, + aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + state := validators.NewMockState(ctrl) + state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) + state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( + vdrSet, nil, + ) + + client := NewMockSignatureGetter(ctrl) + client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(nonVdrSig, nil) + client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(nonVdrSig, nil) + client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(nonVdrSig, nil) + return New(subnetID, state, client) + }, + unsignedMsg: unsignedMsg, + quorumNum: 1, + expectedErr: avalancheWarp.ErrInsufficientWeight, + }, + { + name: "3/3 validators reply with signature; 2 invalid signatures; insufficient weight", + contextFunc: context.Background, + aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + state := validators.NewMockState(ctrl) + state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) + state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( + vdrSet, nil, + ) + + client := NewMockSignatureGetter(ctrl) + client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(nonVdrSig, nil) + client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(nonVdrSig, nil) + client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(sig3, nil) + return New(subnetID, state, client) + }, + unsignedMsg: unsignedMsg, + quorumNum: 40, + expectedErr: avalancheWarp.ErrInsufficientWeight, + }, + { + name: "2/3 validators reply with signature; 1 invalid signature; sufficient weight", + contextFunc: context.Background, + aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + state := validators.NewMockState(ctrl) + state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) + state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( + vdrSet, nil, + ) + + client := NewMockSignatureGetter(ctrl) + client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(nonVdrSig, nil) + client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(nil, errTest) + client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(sig3, nil) + return New(subnetID, state, client) + }, + unsignedMsg: unsignedMsg, + quorumNum: 30, + expectedSigners: []*avalancheWarp.Validator{vdr3}, + expectedErr: nil, + }, + { + name: "early termination of signature fetching on parent context cancelation", + contextFunc: func() context.Context { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + return ctx + }, + aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + state := validators.NewMockState(ctrl) + state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) + state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( + vdrSet, nil, + ) + + // Assert that the context passed into each goroutine is canceled + // because the parent context is canceled. + client := NewMockSignatureGetter(ctrl) + client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).DoAndReturn( + func(ctx context.Context, _ ids.NodeID, _ *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { + <-ctx.Done() + err := ctx.Err() + require.ErrorIs(t, err, context.Canceled) + return nil, err + }, + ) + client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).DoAndReturn( + func(ctx context.Context, _ ids.NodeID, _ *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { + <-ctx.Done() + err := ctx.Err() + require.ErrorIs(t, err, context.Canceled) + return nil, err + }, + ) + client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).DoAndReturn( + func(ctx context.Context, _ ids.NodeID, _ *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { + <-ctx.Done() + err := ctx.Err() + require.ErrorIs(t, err, context.Canceled) + return nil, err + }, + ) + return New(subnetID, state, client) + }, + unsignedMsg: unsignedMsg, + quorumNum: 60, // Require 2/3 validators + expectedSigners: []*avalancheWarp.Validator{vdr1, vdr2}, + expectedErr: avalancheWarp.ErrInsufficientWeight, + }, + { + name: "early termination of signature fetching on passing threshold", + contextFunc: context.Background, + aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + state := validators.NewMockState(ctrl) + state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) + state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( + vdrSet, nil, + ) + + client := NewMockSignatureGetter(ctrl) + client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(sig1, nil) + client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(sig2, nil) + client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).DoAndReturn( + // The aggregator will receive sig1 and sig2 which is sufficient weight, + // so the remaining outstanding goroutine should be cancelled. + func(ctx context.Context, _ ids.NodeID, _ *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { + <-ctx.Done() + err := ctx.Err() + require.ErrorIs(t, err, context.Canceled) + return nil, err + }, + ) + return New(subnetID, state, client) + }, + unsignedMsg: unsignedMsg, + quorumNum: 60, // Require 2/3 validators + expectedSigners: []*avalancheWarp.Validator{vdr1, vdr2}, + expectedErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + require := require.New(t) + + a := tt.aggregatorFunc(ctrl) + + res, err := a.AggregateSignatures(tt.contextFunc(), tt.unsignedMsg, tt.quorumNum) + require.ErrorIs(err, tt.expectedErr) + if err != nil { + return + } + + require.Equal(unsignedMsg, &res.Message.UnsignedMessage) + + expectedSigWeight := uint64(0) + for _, vdr := range tt.expectedSigners { + expectedSigWeight += vdr.Weight + } + require.Equal(expectedSigWeight, res.SignatureWeight) + require.Equal(vdr1.Weight+vdr2.Weight+vdr3.Weight, res.TotalWeight) + + expectedSigs := []*bls.Signature{} + for _, vdr := range tt.expectedSigners { + expectedSigs = append(expectedSigs, vdrToSig[vdr]) + } + expectedSig, err := bls.AggregateSignatures(expectedSigs) + require.NoError(err) + gotBLSSig, ok := res.Message.Signature.(*avalancheWarp.BitSetSignature) + require.True(ok) + require.Equal(bls.SignatureToBytes(expectedSig), gotBLSSig.Signature[:]) + + numSigners, err := res.Message.Signature.NumSigners() + require.NoError(err) + require.Len(tt.expectedSigners, numSigners) + }) + } +} diff --git a/warp/aggregator/mock_signature_getter.go b/warp/aggregator/mock_signature_getter.go new file mode 100644 index 0000000000..f00bb920fa --- /dev/null +++ b/warp/aggregator/mock_signature_getter.go @@ -0,0 +1,53 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ava-labs/subnet-evm/warp/aggregator (interfaces: SignatureGetter) + +// Package aggregator is a generated GoMock package. +package aggregator + +import ( + context "context" + reflect "reflect" + + bls "github.com/ava-labs/avalanchego/utils/crypto/bls" + ids "github.com/ava-labs/avalanchego/ids" + warp "github.com/ava-labs/avalanchego/vms/platformvm/warp" + gomock "go.uber.org/mock/gomock" +) + +// MockSignatureGetter is a mock of SignatureGetter interface. +type MockSignatureGetter struct { + ctrl *gomock.Controller + recorder *MockSignatureGetterMockRecorder +} + +// MockSignatureGetterMockRecorder is the mock recorder for MockSignatureGetter. +type MockSignatureGetterMockRecorder struct { + mock *MockSignatureGetter +} + +// NewMockSignatureGetter creates a new mock instance. +func NewMockSignatureGetter(ctrl *gomock.Controller) *MockSignatureGetter { + mock := &MockSignatureGetter{ctrl: ctrl} + mock.recorder = &MockSignatureGetterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSignatureGetter) EXPECT() *MockSignatureGetterMockRecorder { + return m.recorder +} + +// GetSignature mocks base method. +func (m *MockSignatureGetter) GetSignature(arg0 context.Context, arg1 ids.NodeID, arg2 *warp.UnsignedMessage) (*bls.Signature, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSignature", arg0, arg1, arg2) + ret0, _ := ret[0].(*bls.Signature) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSignature indicates an expected call of GetSignature. +func (mr *MockSignatureGetterMockRecorder) GetSignature(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSignature", reflect.TypeOf((*MockSignatureGetter)(nil).GetSignature), arg0, arg1, arg2) +} diff --git a/warp/aggregator/network_signature_backend.go b/warp/aggregator/network_signature_backend.go index b4079e8a22..6f1cfd2770 100644 --- a/warp/aggregator/network_signature_backend.go +++ b/warp/aggregator/network_signature_backend.go @@ -19,7 +19,7 @@ const ( retryBackoffFactor = 2 ) -var _ SignatureBackend = (*NetworkSigner)(nil) +var _ SignatureGetter = (*NetworkSigner)(nil) type NetworkClient interface { SendAppRequest(nodeID ids.NodeID, message []byte) ([]byte, error) @@ -30,11 +30,11 @@ type NetworkSigner struct { Client NetworkClient } -// FetchWarpSignature attempts to fetch a BLS Signature of [unsignedWarpMessage] from [nodeID] until it succeeds or receives an invalid response +// GetSignature attempts to fetch a BLS Signature of [unsignedWarpMessage] from [nodeID] until it succeeds or receives an invalid response // // Note: this function will continue attempting to fetch the signature from [nodeID] until it receives an invalid value or [ctx] is cancelled. // The caller is responsible to cancel [ctx] if it no longer needs to fetch this signature. -func (s *NetworkSigner) FetchWarpSignature(ctx context.Context, nodeID ids.NodeID, unsignedWarpMessage *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { +func (s *NetworkSigner) GetSignature(ctx context.Context, nodeID ids.NodeID, unsignedWarpMessage *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { signatureReq := message.SignatureRequest{ MessageID: unsignedWarpMessage.ID(), } diff --git a/warp/aggregator/signature_job.go b/warp/aggregator/signature_job.go deleted file mode 100644 index 85fc91ab54..0000000000 --- a/warp/aggregator/signature_job.go +++ /dev/null @@ -1,60 +0,0 @@ -// (c) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package aggregator - -import ( - "context" - "errors" - "fmt" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/crypto/bls" - avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" - "github.com/ethereum/go-ethereum/common/hexutil" -) - -var errInvalidSignature = errors.New("invalid signature") - -// SignatureBackend defines the minimum network interface to perform signature aggregation -type SignatureBackend interface { - // FetchWarpSignature attempts to fetch a BLS Signature from [nodeID] for [unsignedWarpMessage] - FetchWarpSignature(ctx context.Context, nodeID ids.NodeID, unsignedWarpMessage *avalancheWarp.UnsignedMessage) (*bls.Signature, error) -} - -// signatureJob fetches a single signature using the injected dependency SignatureBackend and returns a verified signature of the requested message. -type signatureJob struct { - backend SignatureBackend - msg *avalancheWarp.UnsignedMessage - - nodeID ids.NodeID - publicKey *bls.PublicKey - weight uint64 -} - -func (s *signatureJob) String() string { - return fmt.Sprintf("(NodeID: %s, UnsignedMsgID: %s)", s.nodeID, s.msg.ID()) -} - -func newSignatureJob(backend SignatureBackend, validator *avalancheWarp.Validator, msg *avalancheWarp.UnsignedMessage) *signatureJob { - return &signatureJob{ - backend: backend, - msg: msg, - nodeID: validator.NodeIDs[0], // TODO: update from a single nodeID to the original slice and use extra nodeIDs as backup. - publicKey: validator.PublicKey, - weight: validator.Weight, - } -} - -// Execute attempts to fetch the signature from the nodeID specified in this job and then verifies and returns the signature -func (s *signatureJob) Execute(ctx context.Context) (*bls.Signature, error) { - signature, err := s.backend.FetchWarpSignature(ctx, s.nodeID, s.msg) - if err != nil { - return nil, err - } - - if !bls.Verify(s.publicKey, signature, s.msg.Bytes()) { - return nil, fmt.Errorf("%w: node %s returned invalid signature %s for msg %s", errInvalidSignature, s.nodeID, hexutil.Bytes(bls.SignatureToBytes(signature)), s.msg.ID()) - } - return signature, nil -} diff --git a/warp/aggregator/signature_job_test.go b/warp/aggregator/signature_job_test.go deleted file mode 100644 index 0ed52a0eeb..0000000000 --- a/warp/aggregator/signature_job_test.go +++ /dev/null @@ -1,140 +0,0 @@ -// (c) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package aggregator - -import ( - "context" - "errors" - "testing" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/crypto/bls" - avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" - "github.com/stretchr/testify/require" -) - -type mockFetcher struct { - fetch func(ctx context.Context, nodeID ids.NodeID, unsignedWarpMessage *avalancheWarp.UnsignedMessage) (*bls.Signature, error) -} - -func (m *mockFetcher) FetchWarpSignature(ctx context.Context, nodeID ids.NodeID, unsignedWarpMessage *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { - return m.fetch(ctx, nodeID, unsignedWarpMessage) -} - -var ( - nodeIDs []ids.NodeID - blsSecretKeys []*bls.SecretKey - blsPublicKeys []*bls.PublicKey - networkID uint32 = 54321 - sourceChainID = ids.GenerateTestID() - unsignedMsg *avalancheWarp.UnsignedMessage - blsSignatures []*bls.Signature -) - -func init() { - var err error - unsignedMsg, err = avalancheWarp.NewUnsignedMessage(networkID, sourceChainID, []byte{1, 2, 3}) - if err != nil { - panic(err) - } - for i := 0; i < 5; i++ { - nodeIDs = append(nodeIDs, ids.GenerateTestNodeID()) - - blsSecretKey, err := bls.NewSecretKey() - if err != nil { - panic(err) - } - blsPublicKey := bls.PublicFromSecretKey(blsSecretKey) - blsSignature := bls.Sign(blsSecretKey, unsignedMsg.Bytes()) - blsSecretKeys = append(blsSecretKeys, blsSecretKey) - blsPublicKeys = append(blsPublicKeys, blsPublicKey) - blsSignatures = append(blsSignatures, blsSignature) - } -} - -type signatureJobTest struct { - ctx context.Context - job *signatureJob - expectedSignature *bls.Signature - expectedErr error -} - -func executeSignatureJobTest(t testing.TB, test signatureJobTest) { - t.Helper() - - blsSignature, err := test.job.Execute(test.ctx) - if test.expectedErr != nil { - require.ErrorIs(t, err, test.expectedErr) - return - } - require.NoError(t, err) - require.Equal(t, bls.SignatureToBytes(blsSignature), bls.SignatureToBytes(test.expectedSignature)) -} - -func TestSignatureRequestSuccess(t *testing.T) { - job := newSignatureJob( - &mockFetcher{ - fetch: func(context.Context, ids.NodeID, *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { - return blsSignatures[0], nil - }, - }, - &avalancheWarp.Validator{ - NodeIDs: nodeIDs[:1], - PublicKey: blsPublicKeys[0], - Weight: 10, - }, - unsignedMsg, - ) - - executeSignatureJobTest(t, signatureJobTest{ - ctx: context.Background(), - job: job, - expectedSignature: blsSignatures[0], - }) -} - -func TestSignatureRequestFails(t *testing.T) { - err := errors.New("expected error") - job := newSignatureJob( - &mockFetcher{ - fetch: func(context.Context, ids.NodeID, *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { - return nil, err - }, - }, - &avalancheWarp.Validator{ - NodeIDs: nodeIDs[:1], - PublicKey: blsPublicKeys[0], - Weight: 10, - }, - unsignedMsg, - ) - - executeSignatureJobTest(t, signatureJobTest{ - ctx: context.Background(), - job: job, - expectedErr: err, - }) -} - -func TestSignatureRequestInvalidSignature(t *testing.T) { - job := newSignatureJob( - &mockFetcher{ - fetch: func(context.Context, ids.NodeID, *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { - return blsSignatures[1], nil - }, - }, - &avalancheWarp.Validator{ - NodeIDs: nodeIDs[:1], - PublicKey: blsPublicKeys[0], - Weight: 10, - }, - unsignedMsg, - ) - - executeSignatureJobTest(t, signatureJobTest{ - ctx: context.Background(), - job: job, - expectedErr: errInvalidSignature, - }) -} From 510fa0839d015c1a3269d9d156061e3437eb8082 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Fri, 29 Sep 2023 08:47:00 -0400 Subject: [PATCH 21/52] Predicate results docs (#882) * precompile/results: add readme for predicate results serialization * minor improvements --------- Co-authored-by: Ceyhun Onur --- precompile/results/README.md | 114 +++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 precompile/results/README.md diff --git a/precompile/results/README.md b/precompile/results/README.md new file mode 100644 index 0000000000..c5ccb97431 --- /dev/null +++ b/precompile/results/README.md @@ -0,0 +1,114 @@ +# Results + +The results package defines how to encode `PredicateResults` within the block header's `Extra` data field. + +For more information on the motivation for encoding the results of predicate verification within a block, see [here](../../x/warp/README.md#re-processing-historical-blocks). + +## Serialization + +Note: PredicateResults are encoded using the AvalancheGo codec, which serializes a map by serializing the length of the map as a uint32 and then serializes each key-value pair sequentially. + +PredicateResults: +``` ++---------------------+----------------------------------+-------------------+ +| codecID : uint16 | 2 bytes | ++---------------------+----------------------------------+-------------------+ +| results : map[[32]byte]TxPredicateResults | 4 + size(results) | ++---------------------+----------------------------------+-------------------+ + | 6 + size(results) | + +-------------------+ +``` + +- `codecID` is the codec version used to serialize the payload and is hardcoded to `0x0000` +- `results` is a map of transaction hashes to the corresponding `TxPredicateResults` + +TxPredicateResults +``` ++--------------------+---------------------+------------------------------------+ +| txPredicateResults : map[[20]byte][]byte | 4 + size(txPredicateResults) bytes | ++--------------------+---------------------+------------------------------------+ + | 4 + size(txPredicateResults) bytes | + +------------------------------------+ +``` + +- `txPredicateResults` is a map of precompile addresses to the corresponding byte array returned by the predicate + +### Examples + +#### Empty Predicate Results Map + +``` +// codecID +0x00, 0x00, +// results length +0x00, 0x00, 0x00, 0x00 +``` + +#### Predicate Map with a Single Transaction Result + +``` +// codecID +0x00, 0x00, +// Results length +0x00, 0x00, 0x00, 0x01, +// txHash (key in results map) +0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// TxPredicateResults (value in results map) +// TxPredicateResults length +0x00, 0x00, 0x00, 0x01, +// precompile address (key in TxPredicateResults map) +0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, +// Byte array results (value in TxPredicateResults map) +// Length of bytes result +0x00, 0x00, 0x00, 0x03, +// bytes +0x01, 0x02, 0x03 +``` + +#### Predicate Map with Two Transaction Results + +``` +// codecID +0x00, 0x00, +// Results length +0x00, 0x00, 0x00, 0x02, +// txHash (key in results map) +0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// TxPredicateResults (value in results map) +// TxPredicateResults length +0x00, 0x00, 0x00, 0x01, +// precompile address (key in TxPredicateResults map) +0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, +// Byte array results (value in TxPredicateResults map) +// Length of bytes result +0x00, 0x00, 0x00, 0x03, +// bytes +0x01, 0x02, 0x03 +// txHash2 (key in results map) +0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// TxPredicateResults (value in results map) +// TxPredicateResults length +0x00, 0x00, 0x00, 0x01, +// precompile address (key in TxPredicateResults map) +0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, +// Byte array results (value in TxPredicateResults map) +// Length of bytes result +0x00, 0x00, 0x00, 0x03, +// bytes +0x01, 0x02, 0x03 +``` From 1b077980162cce85cd650afd68befcfc7881453d Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Mon, 2 Oct 2023 07:45:40 -0400 Subject: [PATCH 22/52] replace time.After with timer; add max retry backoff (#919) --- warp/aggregator/network_signature_backend.go | 22 +++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/warp/aggregator/network_signature_backend.go b/warp/aggregator/network_signature_backend.go index 6f1cfd2770..4ed89e826d 100644 --- a/warp/aggregator/network_signature_backend.go +++ b/warp/aggregator/network_signature_backend.go @@ -16,6 +16,7 @@ import ( const ( initialRetryFetchSignatureDelay = 100 * time.Millisecond + maxRetryFetchSignatureDelay = 5 * time.Second retryBackoffFactor = 2 ) @@ -44,17 +45,30 @@ func (s *NetworkSigner) GetSignature(ctx context.Context, nodeID ids.NodeID, uns } delay := initialRetryFetchSignatureDelay - for ctx.Err() == nil { + timer := time.NewTimer(delay) + defer timer.Stop() + for { signatureRes, err := s.Client.SendAppRequest(nodeID, signatureReqBytes) // If the client fails to retrieve a response perform an exponential backoff. // Note: it is up to the caller to ensure that [ctx] is eventually cancelled if err != nil { + // Wait until the retry delay has elapsed before retrying. + if !timer.Stop() { + <-timer.C + } + timer.Reset(delay) + select { case <-ctx.Done(): - break - case <-time.After(delay): + return nil, ctx.Err() + case <-timer.C: } + + // Exponential backoff. delay *= retryBackoffFactor + if delay > maxRetryFetchSignatureDelay { + delay = maxRetryFetchSignatureDelay + } continue } @@ -69,6 +83,4 @@ func (s *NetworkSigner) GetSignature(ctx context.Context, nodeID ids.NodeID, uns } return blsSignature, nil } - - return nil, fmt.Errorf("ctx expired fetching signature for message %s from %s: %w", unsignedWarpMessage.ID(), nodeID, ctx.Err()) } From 075dded498bfedbf1a0d627620a1e87b3a8baf86 Mon Sep 17 00:00:00 2001 From: Meaghan FitzGerald Date: Mon, 2 Oct 2023 11:12:38 -0400 Subject: [PATCH 23/52] Update README.md (#926) Signed-off-by: Meaghan FitzGerald --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aecd78a915..0eaad37c0a 100644 --- a/README.md +++ b/README.md @@ -63,11 +63,12 @@ The Subnet EVM supports the following API namespaces: - `debug` Only the `eth` namespace is enabled by default. -Full documentation for the C-Chain's API can be found [here.](https://docs.avax.network/apis/avalanchego/apis/c-chain) +Subnet EVM is a simplified version of [Coreth VM (C-Chain)](https://github.com/ava-labs/coreth). +Full documentation for the C-Chain's API can be found [here](https://docs.avax.network/apis/avalanchego/apis/c-chain). ## Compatibility -The Subnet EVM is compatible with almost all Ethereum tooling, including [Remix](https://docs.avax.network/dapps/smart-contracts/deploy-a-smart-contract-on-avalanche-using-remix-and-metamask/), [Metamask](https://docs.avax.network/dapps/smart-contracts/deploy-a-smart-contract-on-avalanche-using-remix-and-metamask/) and [Truffle](https://docs.avax.network/dapps/smart-contracts/using-truffle-with-the-avalanche-c-chain/). +The Subnet EVM is compatible with almost all Ethereum tooling, including [Remix](https://docs.avax.network/build/dapp/smart-contracts/remix-deploy), [Metamask](https://docs.avax.network/build/dapp/chain-settings), and [Foundry](https://docs.avax.network/build/dapp/smart-contracts/toolchains/foundry). ## Differences Between Subnet EVM and Coreth From aa058dbf0528364a84ad3203ff72cf0a668834fd Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Mon, 2 Oct 2023 23:55:55 +0300 Subject: [PATCH 24/52] move predicate package and merge with predicate utils (#907) * add get predicate result bytes helpers * move predicate package and merge with predicate utils * update readme * refactor imports & utils * typed errs * add comment * fix import name * fix stutter & same var names as pkg name * rename functions * fix reviews * more rename --- core/evm.go | 9 +- core/predicate_check.go | 10 +- core/predicate_check_test.go | 58 ++++----- core/state/statedb.go | 8 +- core/state_transition.go | 8 +- core/vm/evm.go | 6 +- miner/worker.go | 10 +- params/config.go | 20 ++-- plugin/evm/block.go | 11 +- plugin/evm/syncervm_test.go | 12 +- plugin/evm/vm_warp_test.go | 6 +- precompile/contract/interfaces.go | 2 +- precompile/results/predicate_results.go | 113 ------------------ predicate/README.md | 11 ++ .../predicate_bytes.go | 36 +----- .../predicate_bytes_test.go | 25 ---- predicate/predicate_results.go | 113 ++++++++++++++++++ .../predicate_results_test.go | 44 +++---- .../predicate_slots.go | 5 +- .../predicate => predicate}/predicate_tx.go | 3 +- tests/warp/warp_test.go | 5 +- utils/bytes.go | 26 ++++ utils/bytes_test.go | 28 +++++ utils/predicate/README.md | 11 -- x/warp/config.go | 6 +- x/warp/contract_test.go | 18 +-- x/warp/contract_warp_handler.go | 4 +- x/warp/predicate_test.go | 12 +- 28 files changed, 311 insertions(+), 309 deletions(-) delete mode 100644 precompile/results/predicate_results.go create mode 100644 predicate/README.md rename {utils/predicate => predicate}/predicate_bytes.go (70%) rename {utils/predicate => predicate}/predicate_bytes_test.go (70%) create mode 100644 predicate/predicate_results.go rename {precompile/results => predicate}/predicate_results_test.go (74%) rename {utils/predicate => predicate}/predicate_slots.go (87%) rename {utils/predicate => predicate}/predicate_tx.go (89%) delete mode 100644 utils/predicate/README.md diff --git a/core/evm.go b/core/evm.go index f2b3ae3d55..035d1c9ed3 100644 --- a/core/evm.go +++ b/core/evm.go @@ -32,8 +32,7 @@ import ( "github.com/ava-labs/subnet-evm/consensus" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" - "github.com/ava-labs/subnet-evm/precompile/results" - "github.com/ava-labs/subnet-evm/utils/predicate" + "github.com/ava-labs/subnet-evm/predicate" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" //"github.com/ethereum/go-ethereum/log" @@ -58,7 +57,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common // Prior to the DUpgrade, the VM enforces the extra data is smaller than or // equal to this size. After the DUpgrade, the VM pre-verifies the extra // data past the dynamic fee rollup window is valid. - predicateResults, err := results.ParsePredicateResults(predicateBytes) + predicateResults, err := predicate.ParseResults(predicateBytes) if err != nil { log.Error("failed to parse predicate results creating new block context", "err", err, "extra", header.Extra) // As mentioned above, we pre-verify the extra data to ensure this never happens. @@ -73,11 +72,11 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common // in header.Extra. // This function is used to create a BlockContext when the header Extra data is not fully formed yet and it's more efficient to pass in predicateResults // directly rather than re-encode the latest results when executing each individaul transaction. -func NewEVMBlockContextWithPredicateResults(header *types.Header, chain ChainContext, author *common.Address, predicateResults *results.PredicateResults) vm.BlockContext { +func NewEVMBlockContextWithPredicateResults(header *types.Header, chain ChainContext, author *common.Address, predicateResults *predicate.Results) vm.BlockContext { return newEVMBlockContext(header, chain, author, predicateResults) } -func newEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address, predicateResults *results.PredicateResults) vm.BlockContext { +func newEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address, predicateResults *predicate.Results) vm.BlockContext { var ( beneficiary common.Address baseFee *big.Int diff --git a/core/predicate_check.go b/core/predicate_check.go index 79968bd9e3..80d60f90ee 100644 --- a/core/predicate_check.go +++ b/core/predicate_check.go @@ -10,7 +10,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - predicateutils "github.com/ava-labs/subnet-evm/utils/predicate" + "github.com/ava-labs/subnet-evm/predicate" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) @@ -31,12 +31,12 @@ func CheckPredicates(rules params.Rules, predicateContext *precompileconfig.Pred predicateResults := make(map[common.Address][]byte) // Short circuit early if there are no precompile predicates to verify - if len(rules.Predicates) == 0 { + if len(rules.Predicaters) == 0 { return predicateResults, nil } // Prepare the predicate storage slots from the transaction's access list - predicateArguments := predicateutils.PreparePredicateStorageSlots(rules, tx.AccessList()) + predicateArguments := predicate.PreparePredicateStorageSlots(rules, tx.AccessList()) // If there are no predicates to verify, return early and skip requiring the proposervm block // context to be populated. @@ -51,8 +51,8 @@ func CheckPredicates(rules params.Rules, predicateContext *precompileconfig.Pred for address, predicates := range predicateArguments { // Since [address] is only added to [predicateArguments] when there's a valid predicate in the ruleset // there's no need to check if the predicate exists here. - predicate := rules.Predicates[address] - res := predicate.VerifyPredicate(predicateContext, predicates) + predicaterContract := rules.Predicaters[address] + res := predicaterContract.VerifyPredicate(predicateContext, predicates) log.Debug("predicate verify", "tx", tx.Hash(), "address", address, "res", res) predicateResults[address] = res } diff --git a/core/predicate_check_test.go b/core/predicate_check_test.go index b22e1d4f6a..c585dda6a0 100644 --- a/core/predicate_check_test.go +++ b/core/predicate_check_test.go @@ -69,9 +69,9 @@ func TestCheckPredicate(t *testing.T) { gas: 53000, predicateContext: nil, createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater { - predicate := precompileconfig.NewMockPredicater(gomock.NewController(t)) + predicater := precompileconfig.NewMockPredicater(gomock.NewController(t)) return map[common.Address]precompileconfig.Predicater{ - addr1: predicate, + addr1: predicater, } }, expectedRes: make(map[common.Address][]byte), @@ -83,9 +83,9 @@ func TestCheckPredicate(t *testing.T) { ProposerVMBlockCtx: nil, }, createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater { - predicate := precompileconfig.NewMockPredicater(gomock.NewController(t)) + predicater := precompileconfig.NewMockPredicater(gomock.NewController(t)) return map[common.Address]precompileconfig.Predicater{ - addr1: predicate, + addr1: predicater, } }, expectedRes: make(map[common.Address][]byte), @@ -95,11 +95,11 @@ func TestCheckPredicate(t *testing.T) { gas: 53000, predicateContext: nil, createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater { - predicate := precompileconfig.NewMockPredicater(gomock.NewController(t)) + predicater := precompileconfig.NewMockPredicater(gomock.NewController(t)) arg := common.Hash{1} - predicate.EXPECT().PredicateGas(arg[:]).Return(uint64(0), nil).Times(1) + predicater.EXPECT().PredicateGas(arg[:]).Return(uint64(0), nil).Times(1) return map[common.Address]precompileconfig.Predicater{ - addr1: predicate, + addr1: predicater, } }, accessList: types.AccessList([]types.AccessTuple{ @@ -118,11 +118,11 @@ func TestCheckPredicate(t *testing.T) { ProposerVMBlockCtx: nil, }, createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater { - predicate := precompileconfig.NewMockPredicater(gomock.NewController(t)) + predicater := precompileconfig.NewMockPredicater(gomock.NewController(t)) arg := common.Hash{1} - predicate.EXPECT().PredicateGas(arg[:]).Return(uint64(0), nil).Times(1) + predicater.EXPECT().PredicateGas(arg[:]).Return(uint64(0), nil).Times(1) return map[common.Address]precompileconfig.Predicater{ - addr1: predicate, + addr1: predicater, } }, accessList: types.AccessList([]types.AccessTuple{ @@ -139,12 +139,12 @@ func TestCheckPredicate(t *testing.T) { gas: 53000, predicateContext: predicateContext, createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater { - predicate := precompileconfig.NewMockPredicater(gomock.NewController(t)) + predicater := precompileconfig.NewMockPredicater(gomock.NewController(t)) arg := common.Hash{1} - predicate.EXPECT().PredicateGas(arg[:]).Return(uint64(0), nil).Times(2) - predicate.EXPECT().VerifyPredicate(gomock.Any(), [][]byte{arg[:]}).Return(predicateResultBytes1) + predicater.EXPECT().PredicateGas(arg[:]).Return(uint64(0), nil).Times(2) + predicater.EXPECT().VerifyPredicate(gomock.Any(), [][]byte{arg[:]}).Return(predicateResultBytes1) return map[common.Address]precompileconfig.Predicater{ - addr1: predicate, + addr1: predicater, } }, accessList: types.AccessList([]types.AccessTuple{ @@ -164,11 +164,11 @@ func TestCheckPredicate(t *testing.T) { gas: 53000, predicateContext: predicateContext, createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater { - predicate := precompileconfig.NewMockPredicater(gomock.NewController(t)) + predicater := precompileconfig.NewMockPredicater(gomock.NewController(t)) arg := common.Hash{1} - predicate.EXPECT().PredicateGas(arg[:]).Return(uint64(0), testErr) + predicater.EXPECT().PredicateGas(arg[:]).Return(uint64(0), testErr) return map[common.Address]precompileconfig.Predicater{ - addr1: predicate, + addr1: predicater, } }, accessList: types.AccessList([]types.AccessTuple{ @@ -185,13 +185,13 @@ func TestCheckPredicate(t *testing.T) { gas: 53000, predicateContext: predicateContext, createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater { - predicate := precompileconfig.NewMockPredicater(gomock.NewController(t)) + predicater := precompileconfig.NewMockPredicater(gomock.NewController(t)) arg := common.Hash{1} - predicate.EXPECT().PredicateGas(arg[:]).Return(uint64(0), nil).Times(2) - predicate.EXPECT().VerifyPredicate(gomock.Any(), [][]byte{arg[:]}).Return(predicateResultBytes1) + predicater.EXPECT().PredicateGas(arg[:]).Return(uint64(0), nil).Times(2) + predicater.EXPECT().VerifyPredicate(gomock.Any(), [][]byte{arg[:]}).Return(predicateResultBytes1) return map[common.Address]precompileconfig.Predicater{ - addr1: predicate, - addr2: predicate, + addr1: predicater, + addr2: predicater, } }, accessList: types.AccessList([]types.AccessTuple{ @@ -249,10 +249,10 @@ func TestCheckPredicate(t *testing.T) { gas: 61600, predicateContext: predicateContext, createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater { - predicate := precompileconfig.NewMockPredicater(gomock.NewController(t)) + predicater := precompileconfig.NewMockPredicater(gomock.NewController(t)) return map[common.Address]precompileconfig.Predicater{ - addr1: predicate, - addr2: predicate, + addr1: predicater, + addr2: predicater, } }, accessList: types.AccessList([]types.AccessTuple{ @@ -276,11 +276,11 @@ func TestCheckPredicate(t *testing.T) { gas: 53000, predicateContext: predicateContext, createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater { - predicate := precompileconfig.NewMockPredicater(gomock.NewController(t)) + predicater := precompileconfig.NewMockPredicater(gomock.NewController(t)) arg := common.Hash{1} - predicate.EXPECT().PredicateGas(arg[:]).Return(uint64(1), nil) + predicater.EXPECT().PredicateGas(arg[:]).Return(uint64(1), nil) return map[common.Address]precompileconfig.Predicater{ - addr1: predicate, + addr1: predicater, } }, accessList: types.AccessList([]types.AccessTuple{ @@ -301,7 +301,7 @@ func TestCheckPredicate(t *testing.T) { rules := params.TestChainConfig.AvalancheRules(common.Big0, 0) if test.createPredicates != nil { for address, predicater := range test.createPredicates(t) { - rules.Predicates[address] = predicater + rules.Predicaters[address] = predicater } } diff --git a/core/state/statedb.go b/core/state/statedb.go index 7c6abdbd33..be45092751 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -39,8 +39,8 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/metrics" "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/predicate" "github.com/ava-labs/subnet-evm/trie" - predicateutils "github.com/ava-labs/subnet-evm/utils/predicate" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" @@ -747,8 +747,8 @@ func copyPredicateStorageSlots(predicateStorageSlots map[common.Address][][]byte res := make(map[common.Address][][]byte, len(predicateStorageSlots)) for address, predicates := range predicateStorageSlots { copiedPredicates := make([][]byte, len(predicates)) - for i, predicate := range predicates { - copiedPredicates[i] = common.CopyBytes(predicate) + for i, predicateBytes := range predicates { + copiedPredicates[i] = common.CopyBytes(predicateBytes) } res[address] = copiedPredicates } @@ -1190,7 +1190,7 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d al.AddAddress(coinbase) } - s.predicateStorageSlots = predicateutils.PreparePredicateStorageSlots(rules, list) + s.predicateStorageSlots = predicate.PreparePredicateStorageSlots(rules, list) } // Reset transient storage at the beginning of transaction execution s.transientStorage = newTransientStorage() diff --git a/core/state_transition.go b/core/state_transition.go index 11d2e033f8..ef38fe6717 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -35,7 +35,7 @@ import ( "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" - predicateutils "github.com/ava-labs/subnet-evm/utils/predicate" + "github.com/ava-labs/subnet-evm/utils" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" cmath "github.com/ethereum/go-ethereum/common/math" @@ -136,7 +136,7 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b func accessListGas(rules params.Rules, accessList types.AccessList) (uint64, error) { var gas uint64 - if !rules.PredicatesExist() { + if !rules.PredicatersExist() { gas += uint64(len(accessList)) * params.TxAccessListAddressGas gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas return gas, nil @@ -144,7 +144,7 @@ func accessListGas(rules params.Rules, accessList types.AccessList) (uint64, err for _, accessTuple := range accessList { address := accessTuple.Address - predicate, ok := rules.Predicates[address] + predicaterContract, ok := rules.Predicaters[address] if !ok { // Previous access list gas calculation does not use safemath because an overflow would not be possible with // the size of access lists that could be included in a block and standard access list gas costs. @@ -157,7 +157,7 @@ func accessListGas(rules params.Rules, accessList types.AccessList) (uint64, err } gas = totalGas } else { - predicateGas, err := predicate.PredicateGas(predicateutils.HashSliceToBytes(accessTuple.StorageKeys)) + predicateGas, err := predicaterContract.PredicateGas(utils.HashSliceToBytes(accessTuple.StorageKeys)) if err != nil { return 0, err } diff --git a/core/vm/evm.go b/core/vm/evm.go index 0bca2fd116..75273c9796 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -38,7 +38,7 @@ import ( "github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist" "github.com/ava-labs/subnet-evm/precompile/modules" "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ava-labs/subnet-evm/precompile/results" + "github.com/ava-labs/subnet-evm/predicate" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -114,7 +114,7 @@ type BlockContext struct { GetHash GetHashFunc // PredicateResults are the results of predicate verification available throughout the EVM's execution. // PredicateResults may be nil if it is not encoded in the block's header. - PredicateResults *results.PredicateResults + PredicateResults *predicate.Results // Block information Coinbase common.Address // Provides information for COINBASE @@ -137,7 +137,7 @@ func (b *BlockContext) GetPredicateResults(txHash common.Hash, address common.Ad if b.PredicateResults == nil { return nil } - return b.PredicateResults.GetPredicateResults(txHash, address) + return b.PredicateResults.GetResults(txHash, address) } // TxContext provides the EVM with information about a transaction. diff --git a/miner/worker.go b/miner/worker.go index 9d83be0020..a9ac8952fb 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -46,7 +46,7 @@ import ( "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ava-labs/subnet-evm/precompile/results" + "github.com/ava-labs/subnet-evm/predicate" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" @@ -76,7 +76,7 @@ type environment struct { // The results are accumulated as transactions are executed by the miner and set on the BlockContext. // If a transaction is dropped, its results must explicitly be removed from predicateResults in the same // way that the gas pool and state is reset. - predicateResults *results.PredicateResults + predicateResults *predicate.Results start time.Time // Time that block building began } @@ -240,7 +240,7 @@ func (w *worker) createCurrentEnvironment(predicateContext *precompileconfig.Pre gasPool: new(core.GasPool).AddGas(header.GasLimit), rules: w.chainConfig.AvalancheRules(header.Number, header.Time), predicateContext: predicateContext, - predicateResults: results.NewPredicateResults(), + predicateResults: predicate.NewResults(), start: tstart, }, nil } @@ -258,7 +258,7 @@ func (w *worker) commitTransaction(env *environment, tx *types.Transaction, coin log.Debug("Transaction predicate failed verification in miner", "tx", tx.Hash(), "err", err) return nil, err } - env.predicateResults.SetTxPredicateResults(tx.Hash(), results) + env.predicateResults.SetTxResults(tx.Hash(), results) blockContext = core.NewEVMBlockContextWithPredicateResults(env.header, w.chain, &coinbase, env.predicateResults) } else { @@ -269,7 +269,7 @@ func (w *worker) commitTransaction(env *environment, tx *types.Transaction, coin if err != nil { env.state.RevertToSnapshot(snap) env.gasPool.SetGas(gp) - env.predicateResults.DeleteTxPredicateResults(tx.Hash()) + env.predicateResults.DeleteTxResults(tx.Hash()) return nil, err } env.txs = append(env.txs, tx) diff --git a/params/config.go b/params/config.go index a4cc2f1635..ed15a67c8e 100644 --- a/params/config.go +++ b/params/config.go @@ -385,13 +385,13 @@ func (c *ChainConfig) IsDUpgrade(time uint64) bool { return utils.IsTimestampForked(c.DUpgradeTimestamp, time) } -func (r *Rules) PredicatesExist() bool { - return len(r.Predicates) > 0 +func (r *Rules) PredicatersExist() bool { + return len(r.Predicaters) > 0 } -func (r *Rules) PredicateExists(addr common.Address) bool { - _, predicateExists := r.Predicates[addr] - return predicateExists +func (r *Rules) PredicaterExists(addr common.Address) bool { + _, PredicaterExists := r.Predicaters[addr] + return PredicaterExists } // IsPrecompileEnabled returns whether precompile with [address] is enabled at [timestamp]. @@ -719,9 +719,9 @@ type Rules struct { // Note: none of these addresses should conflict with the address space used by // any existing precompiles. ActivePrecompiles map[common.Address]precompileconfig.Config - // Predicates maps addresses to stateful precompile predicate functions + // Predicaters maps addresses to stateful precompile Predicaters // that are enabled for this rule set. - Predicates map[common.Address]precompileconfig.Predicater + Predicaters map[common.Address]precompileconfig.Predicater // AccepterPrecompiles map addresses to stateful precompile accepter functions // that are enabled for this rule set. AccepterPrecompiles map[common.Address]precompileconfig.Accepter @@ -762,13 +762,13 @@ func (c *ChainConfig) AvalancheRules(blockNum *big.Int, timestamp uint64) Rules // Initialize the stateful precompiles that should be enabled at [blockTimestamp]. rules.ActivePrecompiles = make(map[common.Address]precompileconfig.Config) - rules.Predicates = make(map[common.Address]precompileconfig.Predicater) + rules.Predicaters = make(map[common.Address]precompileconfig.Predicater) rules.AccepterPrecompiles = make(map[common.Address]precompileconfig.Accepter) for _, module := range modules.RegisteredModules() { if config := c.getActivePrecompileConfig(module.Address, timestamp); config != nil && !config.IsDisabled() { rules.ActivePrecompiles[module.Address] = config - if predicate, ok := config.(precompileconfig.Predicater); ok { - rules.Predicates[module.Address] = predicate + if predicater, ok := config.(precompileconfig.Predicater); ok { + rules.Predicaters[module.Address] = predicater } if precompileAccepter, ok := config.(precompileconfig.Accepter); ok { rules.AccepterPrecompiles[module.Address] = precompileAccepter diff --git a/plugin/evm/block.go b/plugin/evm/block.go index a16bc894ed..9c84622cf2 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -18,8 +18,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ava-labs/subnet-evm/precompile/results" - "github.com/ava-labs/subnet-evm/utils/predicate" + "github.com/ava-labs/subnet-evm/predicate" "github.com/ava-labs/subnet-evm/warp/payload" "github.com/ava-labs/subnet-evm/x/warp" @@ -201,7 +200,7 @@ func (b *Block) Verify(context.Context) error { // ShouldVerifyWithContext implements the block.WithVerifyContext interface func (b *Block) ShouldVerifyWithContext(context.Context) (bool, error) { - predicates := b.vm.chainConfig.AvalancheRules(b.ethBlock.Number(), b.ethBlock.Timestamp()).Predicates + predicates := b.vm.chainConfig.AvalancheRules(b.ethBlock.Number(), b.ethBlock.Timestamp()).Predicaters // Short circuit early if there are no predicates to verify if len(predicates) == 0 { return false, nil @@ -270,19 +269,19 @@ func (b *Block) verifyPredicates(predicateContext *precompileconfig.PredicateCon rules := b.vm.chainConfig.AvalancheRules(b.ethBlock.Number(), b.ethBlock.Timestamp()) switch { - case !rules.IsDUpgrade && rules.PredicatesExist(): + case !rules.IsDUpgrade && rules.PredicatersExist(): return errors.New("cannot enable predicates before DUpgrade activation") case !rules.IsDUpgrade: return nil } - predicateResults := results.NewPredicateResults() + predicateResults := predicate.NewResults() for _, tx := range b.ethBlock.Transactions() { results, err := core.CheckPredicates(rules, predicateContext, tx) if err != nil { return err } - predicateResults.SetTxPredicateResults(tx.Hash(), results) + predicateResults.SetTxResults(tx.Hash(), results) } // TODO: document required gas constraints to ensure marshalling predicate results does not error predicateResultsBytes, err := predicateResults.Bytes() diff --git a/plugin/evm/syncervm_test.go b/plugin/evm/syncervm_test.go index 451af4a949..abade7083c 100644 --- a/plugin/evm/syncervm_test.go +++ b/plugin/evm/syncervm_test.go @@ -32,7 +32,7 @@ import ( "github.com/ava-labs/subnet-evm/ethdb" "github.com/ava-labs/subnet-evm/metrics" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile/results" + "github.com/ava-labs/subnet-evm/predicate" statesyncclient "github.com/ava-labs/subnet-evm/sync/client" "github.com/ava-labs/subnet-evm/sync/statesync" "github.com/ava-labs/subnet-evm/trie" @@ -264,9 +264,7 @@ func TestVMShutdownWhileSyncing(t *testing.T) { } func createSyncServerAndClientVMs(t *testing.T, test syncTest) *syncVMSetup { - var ( - serverVM, syncerVM *VM - ) + var serverVM, syncerVM *VM // If there is an error shutdown the VMs if they have been instantiated defer func() { // If the test has not already failed, shut down the VMs since the caller @@ -293,7 +291,7 @@ func createSyncServerAndClientVMs(t *testing.T, test syncTest) *syncVMSetup { // configure [serverVM] _, serverVM, _, serverAppSender := GenesisVM(t, true, genesisJSONLatest, "", "") generateAndAcceptBlocks(t, serverVM, parentsToGet, func(i int, gen *core.BlockGen) { - b, err := results.NewPredicateResults().Bytes() + b, err := predicate.NewResults().Bytes() if err != nil { t.Fatal(err) } @@ -469,7 +467,7 @@ func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) { txsPerBlock := 10 toAddress := testEthAddrs[1] // arbitrary choice generateAndAcceptBlocks(t, syncerVM, blocksToBuild, func(_ int, gen *core.BlockGen) { - b, err := results.NewPredicateResults().Bytes() + b, err := predicate.NewResults().Bytes() if err != nil { t.Fatal(err) } @@ -495,7 +493,7 @@ func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) { // Generate blocks after we have entered normal consensus as well generateAndAcceptBlocks(t, syncerVM, blocksToBuild, func(_ int, gen *core.BlockGen) { - b, err := results.NewPredicateResults().Bytes() + b, err := predicate.NewResults().Bytes() if err != nil { t.Fatal(err) } diff --git a/plugin/evm/vm_warp_test.go b/plugin/evm/vm_warp_test.go index 1f010ecb79..3a00d3889f 100644 --- a/plugin/evm/vm_warp_test.go +++ b/plugin/evm/vm_warp_test.go @@ -27,8 +27,8 @@ import ( "github.com/ava-labs/subnet-evm/eth/tracers" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/predicate" subnetEVMUtils "github.com/ava-labs/subnet-evm/utils" - predicateutils "github.com/ava-labs/subnet-evm/utils/predicate" warpPayload "github.com/ava-labs/subnet-evm/warp/payload" "github.com/ava-labs/subnet-evm/x/warp" "github.com/ethereum/go-ethereum/common" @@ -316,7 +316,7 @@ func testWarpVMTransaction(t *testing.T, unsignedMessage *avalancheWarp.Unsigned exampleWarpAddress := crypto.CreateAddress(testEthAddrs[0], 0) tx, err := types.SignTx( - predicateutils.NewPredicateTx( + predicate.NewPredicateTx( vm.chainConfig.ChainID, 1, &exampleWarpAddress, @@ -482,7 +482,7 @@ func TestReceiveWarpMessage(t *testing.T) { getWarpMsgInput, err := warp.PackGetVerifiedWarpMessage(0) require.NoError(err) getVerifiedWarpMessageTx, err := types.SignTx( - predicateutils.NewPredicateTx( + predicate.NewPredicateTx( vm.chainConfig.ChainID, 0, &warp.Module.Address, diff --git a/precompile/contract/interfaces.go b/precompile/contract/interfaces.go index f3ce0a9e13..7970234029 100644 --- a/precompile/contract/interfaces.go +++ b/precompile/contract/interfaces.go @@ -62,7 +62,7 @@ type ConfigurationBlockContext interface { type BlockContext interface { ConfigurationBlockContext - // GetPredicateResuls returns an arbitrary byte array result of verifying the predicates + // GetResults returns an arbitrary byte array result of verifying the predicates // of the given transaction, precompile address pair. GetPredicateResults(txHash common.Hash, precompileAddress common.Address) []byte } diff --git a/precompile/results/predicate_results.go b/precompile/results/predicate_results.go deleted file mode 100644 index ed4e996e92..0000000000 --- a/precompile/results/predicate_results.go +++ /dev/null @@ -1,113 +0,0 @@ -// (c) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package results - -import ( - "fmt" - "strings" - - "github.com/ava-labs/avalanchego/codec" - "github.com/ava-labs/avalanchego/codec/linearcodec" - "github.com/ava-labs/avalanchego/utils/units" - "github.com/ava-labs/avalanchego/utils/wrappers" - "github.com/ethereum/go-ethereum/common" -) - -const ( - Version = uint16(0) - MaxResultsSize = units.MiB -) - -var Codec codec.Manager - -func init() { - Codec = codec.NewManager(MaxResultsSize) - - c := linearcodec.NewDefault() - errs := wrappers.Errs{} - errs.Add( - c.RegisterType(PredicateResults{}), - Codec.RegisterCodec(Version, c), - ) - if errs.Errored() { - panic(errs.Err) - } -} - -// TxPredicateResults is a map of results for each precompile address to the resulting byte array. -type TxPredicateResults map[common.Address][]byte - -// PredicateResults encodes the precompile predicate results included in a block on a per transaction basis. -// PredicateResults is not thread-safe. -type PredicateResults struct { - Results map[common.Hash]TxPredicateResults `serialize:"true"` -} - -// NewPredicateResults returns an empty predicate results. -func NewPredicateResults() *PredicateResults { - return &PredicateResults{ - Results: make(map[common.Hash]TxPredicateResults), - } -} - -func NewPredicateResultsFromMap(results map[common.Hash]TxPredicateResults) *PredicateResults { - return &PredicateResults{ - Results: results, - } -} - -// ParsePredicateResults parses [b] into predicate results. -func ParsePredicateResults(b []byte) (*PredicateResults, error) { - res := new(PredicateResults) - parsedVersion, err := Codec.Unmarshal(b, res) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal predicate results: %w", err) - } - if parsedVersion != Version { - return nil, fmt.Errorf("invalid version (found %d, expected %d)", parsedVersion, Version) - } - return res, nil -} - -// GetPredicateResults returns the byte array results for [txHash] from precompile [address] if available. -func (p *PredicateResults) GetPredicateResults(txHash common.Hash, address common.Address) []byte { - txResults, ok := p.Results[txHash] - if !ok { - return nil - } - return txResults[address] -} - -// SetTxPredicateResults sets the predicate results for the given [txHash]. Overrides results if present. -func (p *PredicateResults) SetTxPredicateResults(txHash common.Hash, txResults TxPredicateResults) { - // If there are no tx results, don't store an entry in the map - if len(txResults) == 0 { - delete(p.Results, txHash) - return - } - p.Results[txHash] = txResults -} - -// DeleteTxPredicateResults deletes the predicate results for the given [txHash]. -func (p *PredicateResults) DeleteTxPredicateResults(txHash common.Hash) { - delete(p.Results, txHash) -} - -// Bytes marshals the current state of predicate results -func (p *PredicateResults) Bytes() ([]byte, error) { - return Codec.Marshal(Version, p) -} - -func (p *PredicateResults) String() string { - sb := strings.Builder{} - - sb.WriteString(fmt.Sprintf("PredicateResults: (Size = %d)", len(p.Results))) - for txHash, results := range p.Results { - for address, result := range results { - sb.WriteString(fmt.Sprintf("\n%s %s: %x", txHash, address, result)) - } - } - - return sb.String() -} diff --git a/predicate/README.md b/predicate/README.md new file mode 100644 index 0000000000..b35cbfd145 --- /dev/null +++ b/predicate/README.md @@ -0,0 +1,11 @@ +# Predicate + +This package contains the predicate data structure and its encoding and helper functions to unpack/pack the data structure. + +## Encoding + +A byte slice of size N is encoded as: + +1. Slice of N bytes +2. Delimiter byte `0xff` +3. Appended 0s to the nearest multiple of 32 bytes diff --git a/utils/predicate/predicate_bytes.go b/predicate/predicate_bytes.go similarity index 70% rename from utils/predicate/predicate_bytes.go rename to predicate/predicate_bytes.go index 2e1b3690e8..d86e624d85 100644 --- a/utils/predicate/predicate_bytes.go +++ b/predicate/predicate_bytes.go @@ -10,12 +10,12 @@ import ( "github.com/ethereum/go-ethereum/common" ) -// PredicateEndByte is used as a delimiter for the bytes packed into a precompile predicate. +// EndByte is used as a delimiter for the bytes packed into a precompile predicate. // Precompile predicates are encoded in the Access List of transactions in the access tuples // which means that its length must be a multiple of 32 (common.HashLength). // For messages with a length that does not comply to that, this delimiter is used to // append/remove padding. -var PredicateEndByte = byte(0xff) +var EndByte = byte(0xff) var ( ErrInvalidAllZeroBytes = fmt.Errorf("predicate specified invalid all zero bytes") @@ -26,9 +26,9 @@ var ( // PackPredicate packs [predicate] by delimiting the actual message with [PredicateEndByte] // and zero padding to reach a length that is a multiple of 32. -func PackPredicate(predicate []byte) []byte { - predicate = append(predicate, PredicateEndByte) - return common.RightPadBytes(predicate, (len(predicate)+31)/32*32) +func PackPredicate(predicateBytes []byte) []byte { + predicateBytes = append(predicateBytes, EndByte) + return common.RightPadBytes(predicateBytes, (len(predicateBytes)+31)/32*32) } // UnpackPredicate unpacks a predicate by stripping right padded zeroes, checking for the delimter, @@ -44,37 +44,13 @@ func UnpackPredicate(paddedPredicate []byte) ([]byte, error) { return nil, fmt.Errorf("%w: got length (%d), expected length (%d)", ErrInvalidPadding, len(paddedPredicate), expectedPaddedLength) } - if trimmedPredicateBytes[len(trimmedPredicateBytes)-1] != PredicateEndByte { + if trimmedPredicateBytes[len(trimmedPredicateBytes)-1] != EndByte { return nil, ErrInvalidEndDelimiter } return trimmedPredicateBytes[:len(trimmedPredicateBytes)-1], nil } -// HashSliceToBytes serializes a []common.Hash into a tightly packed byte array. -func HashSliceToBytes(hashes []common.Hash) []byte { - bytes := make([]byte, common.HashLength*len(hashes)) - for i, hash := range hashes { - copy(bytes[i*common.HashLength:], hash[:]) - } - return bytes -} - -// BytesToHashSlice packs [b] into a slice of hash values with zero padding -// to the right if the length of b is not a multiple of 32. -func BytesToHashSlice(b []byte) []common.Hash { - var ( - numHashes = (len(b) + 31) / 32 - hashes = make([]common.Hash, numHashes) - ) - - for i := range hashes { - start := i * common.HashLength - copy(hashes[i][:], b[start:]) - } - return hashes -} - // GetPredicateResultBytes returns the predicate result bytes from the extra data. // If the extra data does not contain predicate result bytes, an error is returned. func GetPredicateResultBytes(extraData []byte) ([]byte, error) { diff --git a/utils/predicate/predicate_bytes_test.go b/predicate/predicate_bytes_test.go similarity index 70% rename from utils/predicate/predicate_bytes_test.go rename to predicate/predicate_bytes_test.go index 6200171e98..9742ed2858 100644 --- a/utils/predicate/predicate_bytes_test.go +++ b/predicate/predicate_bytes_test.go @@ -11,31 +11,6 @@ import ( "github.com/stretchr/testify/require" ) -func testBytesToHashSlice(t testing.TB, b []byte) { - hashSlice := BytesToHashSlice(b) - - copiedBytes := HashSliceToBytes(hashSlice) - - if len(b)%32 == 0 { - require.Equal(t, b, copiedBytes) - } else { - require.Equal(t, b, copiedBytes[:len(b)]) - // Require that any additional padding is all zeroes - padding := copiedBytes[len(b):] - require.Equal(t, bytes.Repeat([]byte{0x00}, len(padding)), padding) - } -} - -func FuzzHashSliceToBytes(f *testing.F) { - for i := 0; i < 100; i++ { - f.Add(utils.RandomBytes(i)) - } - - f.Fuzz(func(t *testing.T, b []byte) { - testBytesToHashSlice(t, b) - }) -} - func testPackPredicate(t testing.TB, b []byte) { packedPredicate := PackPredicate(b) unpackedPredicated, err := UnpackPredicate(packedPredicate) diff --git a/predicate/predicate_results.go b/predicate/predicate_results.go new file mode 100644 index 0000000000..f87b181f44 --- /dev/null +++ b/predicate/predicate_results.go @@ -0,0 +1,113 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package predicate + +import ( + "fmt" + "strings" + + "github.com/ava-labs/avalanchego/codec" + "github.com/ava-labs/avalanchego/codec/linearcodec" + "github.com/ava-labs/avalanchego/utils/units" + "github.com/ava-labs/avalanchego/utils/wrappers" + "github.com/ethereum/go-ethereum/common" +) + +const ( + Version = uint16(0) + MaxResultsSize = units.MiB +) + +var Codec codec.Manager + +func init() { + Codec = codec.NewManager(MaxResultsSize) + + c := linearcodec.NewDefault() + errs := wrappers.Errs{} + errs.Add( + c.RegisterType(Results{}), + Codec.RegisterCodec(Version, c), + ) + if errs.Errored() { + panic(errs.Err) + } +} + +// TxResults is a map of results for each precompile address to the resulting byte array. +type TxResults map[common.Address][]byte + +// Results encodes the precompile predicate results included in a block on a per transaction basis. +// Results is not thread-safe. +type Results struct { + Results map[common.Hash]TxResults `serialize:"true"` +} + +// NewResults returns an empty predicate results. +func NewResults() *Results { + return &Results{ + Results: make(map[common.Hash]TxResults), + } +} + +func NewResultsFromMap(results map[common.Hash]TxResults) *Results { + return &Results{ + Results: results, + } +} + +// ParseResults parses [b] into predicate results. +func ParseResults(b []byte) (*Results, error) { + res := new(Results) + parsedVersion, err := Codec.Unmarshal(b, res) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal predicate results: %w", err) + } + if parsedVersion != Version { + return nil, fmt.Errorf("invalid version (found %d, expected %d)", parsedVersion, Version) + } + return res, nil +} + +// GetResults returns the byte array results for [txHash] from precompile [address] if available. +func (r *Results) GetResults(txHash common.Hash, address common.Address) []byte { + txResults, ok := r.Results[txHash] + if !ok { + return nil + } + return txResults[address] +} + +// SetTxResults sets the predicate results for the given [txHash]. Overrides results if present. +func (r *Results) SetTxResults(txHash common.Hash, txResults TxResults) { + // If there are no tx results, don't store an entry in the map + if len(txResults) == 0 { + delete(r.Results, txHash) + return + } + r.Results[txHash] = txResults +} + +// DeleteTxResults deletes the predicate results for the given [txHash]. +func (r *Results) DeleteTxResults(txHash common.Hash) { + delete(r.Results, txHash) +} + +// Bytes marshals the current state of predicate results +func (r *Results) Bytes() ([]byte, error) { + return Codec.Marshal(Version, r) +} + +func (r *Results) String() string { + sb := strings.Builder{} + + sb.WriteString(fmt.Sprintf("PredicateResults: (Size = %d)", len(r.Results))) + for txHash, results := range r.Results { + for address, result := range results { + sb.WriteString(fmt.Sprintf("\n%s %s: %x", txHash, address, result)) + } + } + + return sb.String() +} diff --git a/precompile/results/predicate_results_test.go b/predicate/predicate_results_test.go similarity index 74% rename from precompile/results/predicate_results_test.go rename to predicate/predicate_results_test.go index 22088c4b1f..e3daefa706 100644 --- a/precompile/results/predicate_results_test.go +++ b/predicate/predicate_results_test.go @@ -1,7 +1,7 @@ // (c) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package results +package predicate import ( "testing" @@ -12,22 +12,22 @@ import ( func TestPredicateResultsParsing(t *testing.T) { type test struct { - results map[common.Hash]TxPredicateResults + results map[common.Hash]TxResults expectedHex string } for name, test := range map[string]test{ "empty": { - results: make(map[common.Hash]TxPredicateResults), + results: make(map[common.Hash]TxResults), expectedHex: "000000000000", }, "single tx no results": { - results: map[common.Hash]TxPredicateResults{ + results: map[common.Hash]TxResults{ {1}: map[common.Address][]byte{}, }, expectedHex: "000000000001010000000000000000000000000000000000000000000000000000000000000000000000", }, "single tx single result": { - results: map[common.Hash]TxPredicateResults{ + results: map[common.Hash]TxResults{ {1}: map[common.Address][]byte{ {2}: {1, 2, 3}, }, @@ -35,7 +35,7 @@ func TestPredicateResultsParsing(t *testing.T) { expectedHex: "000000000001010000000000000000000000000000000000000000000000000000000000000000000001020000000000000000000000000000000000000000000003010203", }, "single tx multiple results": { - results: map[common.Hash]TxPredicateResults{ + results: map[common.Hash]TxResults{ {1}: map[common.Address][]byte{ {2}: {1, 2, 3}, {3}: {1, 2, 3}, @@ -44,14 +44,14 @@ func TestPredicateResultsParsing(t *testing.T) { expectedHex: "000000000001010000000000000000000000000000000000000000000000000000000000000000000002020000000000000000000000000000000000000000000003010203030000000000000000000000000000000000000000000003010203", }, "multiple txs no result": { - results: map[common.Hash]TxPredicateResults{ + results: map[common.Hash]TxResults{ {1}: map[common.Address][]byte{}, {2}: map[common.Address][]byte{}, }, expectedHex: "000000000002010000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000", }, "multiple txs single result": { - results: map[common.Hash]TxPredicateResults{ + results: map[common.Hash]TxResults{ {1}: map[common.Address][]byte{ {2}: {1, 2, 3}, }, @@ -62,7 +62,7 @@ func TestPredicateResultsParsing(t *testing.T) { expectedHex: "000000000002010000000000000000000000000000000000000000000000000000000000000000000001020000000000000000000000000000000000000000000003010203020000000000000000000000000000000000000000000000000000000000000000000001030000000000000000000000000000000000000000000003030201", }, "multiple txs multiple results": { - results: map[common.Hash]TxPredicateResults{ + results: map[common.Hash]TxResults{ {1}: map[common.Address][]byte{ {2}: {1, 2, 3}, {3}: {3, 2, 1}, @@ -75,7 +75,7 @@ func TestPredicateResultsParsing(t *testing.T) { expectedHex: "000000000002010000000000000000000000000000000000000000000000000000000000000000000002020000000000000000000000000000000000000000000003010203030000000000000000000000000000000000000000000003030201020000000000000000000000000000000000000000000000000000000000000000000002020000000000000000000000000000000000000000000003010203030000000000000000000000000000000000000000000003030201", }, "multiple txs mixed results": { - results: map[common.Hash]TxPredicateResults{ + results: map[common.Hash]TxResults{ {1}: map[common.Address][]byte{ {2}: {1, 2, 3}, }, @@ -90,11 +90,11 @@ func TestPredicateResultsParsing(t *testing.T) { } { t.Run(name, func(t *testing.T) { require := require.New(t) - predicateResults := NewPredicateResultsFromMap(test.results) + predicateResults := NewResultsFromMap(test.results) b, err := predicateResults.Bytes() require.NoError(err) - parsedPredicateResults, err := ParsePredicateResults(b) + parsedPredicateResults, err := ParseResults(b) require.NoError(err) require.Equal(predicateResults, parsedPredicateResults) require.Equal(test.expectedHex, common.Bytes2Hex(b)) @@ -105,7 +105,7 @@ func TestPredicateResultsParsing(t *testing.T) { func TestPredicateResultsAccessors(t *testing.T) { require := require.New(t) - predicateResults := NewPredicateResults() + predicateResults := NewResults() txHash := common.Hash{1} addr := common.Address{2} @@ -114,15 +114,15 @@ func TestPredicateResultsAccessors(t *testing.T) { addr: predicateResult, } - require.Empty(predicateResults.GetPredicateResults(txHash, addr)) - predicateResults.SetTxPredicateResults(txHash, txPredicateResults) - require.Equal(predicateResult, predicateResults.GetPredicateResults(txHash, addr)) - predicateResults.DeleteTxPredicateResults(txHash) - require.Empty(predicateResults.GetPredicateResults(txHash, addr)) + require.Empty(predicateResults.GetResults(txHash, addr)) + predicateResults.SetTxResults(txHash, txPredicateResults) + require.Equal(predicateResult, predicateResults.GetResults(txHash, addr)) + predicateResults.DeleteTxResults(txHash) + require.Empty(predicateResults.GetResults(txHash, addr)) // Ensure setting empty tx predicate results removes the entry - predicateResults.SetTxPredicateResults(txHash, txPredicateResults) - require.Equal(predicateResult, predicateResults.GetPredicateResults(txHash, addr)) - predicateResults.SetTxPredicateResults(txHash, map[common.Address][]byte{}) - require.Empty(predicateResults.GetPredicateResults(txHash, addr)) + predicateResults.SetTxResults(txHash, txPredicateResults) + require.Equal(predicateResult, predicateResults.GetResults(txHash, addr)) + predicateResults.SetTxResults(txHash, map[common.Address][]byte{}) + require.Empty(predicateResults.GetResults(txHash, addr)) } diff --git a/utils/predicate/predicate_slots.go b/predicate/predicate_slots.go similarity index 87% rename from utils/predicate/predicate_slots.go rename to predicate/predicate_slots.go index 6dbac08bb9..b8a7d7d519 100644 --- a/utils/predicate/predicate_slots.go +++ b/predicate/predicate_slots.go @@ -6,6 +6,7 @@ package predicate import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" ) @@ -16,10 +17,10 @@ import ( func PreparePredicateStorageSlots(rules params.Rules, list types.AccessList) map[common.Address][][]byte { predicateStorageSlots := make(map[common.Address][][]byte) for _, el := range list { - if !rules.PredicateExists(el.Address) { + if !rules.PredicaterExists(el.Address) { continue } - predicateStorageSlots[el.Address] = append(predicateStorageSlots[el.Address], HashSliceToBytes(el.StorageKeys)) + predicateStorageSlots[el.Address] = append(predicateStorageSlots[el.Address], utils.HashSliceToBytes(el.StorageKeys)) } return predicateStorageSlots diff --git a/utils/predicate/predicate_tx.go b/predicate/predicate_tx.go similarity index 89% rename from utils/predicate/predicate_tx.go rename to predicate/predicate_tx.go index f7a0e73c11..5244483888 100644 --- a/utils/predicate/predicate_tx.go +++ b/predicate/predicate_tx.go @@ -7,6 +7,7 @@ import ( "math/big" "github.com/ava-labs/subnet-evm/core/types" + "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" ) @@ -27,7 +28,7 @@ func NewPredicateTx( ) *types.Transaction { accessList = append(accessList, types.AccessTuple{ Address: predicateAddress, - StorageKeys: BytesToHashSlice(PackPredicate(predicateBytes)), + StorageKeys: utils.BytesToHashSlice(PackPredicate(predicateBytes)), }) return types.NewTx(&types.DynamicFeeTx{ ChainID: chainID, diff --git a/tests/warp/warp_test.go b/tests/warp/warp_test.go index 2de777af29..345825d90d 100644 --- a/tests/warp/warp_test.go +++ b/tests/warp/warp_test.go @@ -24,9 +24,9 @@ import ( "github.com/ava-labs/subnet-evm/interfaces" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/plugin/evm" + "github.com/ava-labs/subnet-evm/predicate" "github.com/ava-labs/subnet-evm/tests/utils" "github.com/ava-labs/subnet-evm/tests/utils/runner" - predicateutils "github.com/ava-labs/subnet-evm/utils/predicate" warpBackend "github.com/ava-labs/subnet-evm/warp" "github.com/ava-labs/subnet-evm/x/warp" "github.com/ethereum/go-ethereum/common" @@ -181,7 +181,6 @@ var _ = ginkgo.Describe("[Warp]", ginkgo.Ordered, func() { log.Info("Creating ethclient for blockchainB", "wsURI", chainBWSURI) chainBWSClient, err = ethclient.Dial(chainBWSURI) gomega.Expect(err).Should(gomega.BeNil()) - }) // Send a transaction to Subnet A to issue a Warp Message to Subnet B @@ -345,7 +344,7 @@ var _ = ginkgo.Describe("[Warp]", ginkgo.Ordered, func() { packedInput, err := warp.PackGetVerifiedWarpMessage(0) gomega.Expect(err).Should(gomega.BeNil()) - tx := predicateutils.NewPredicateTx( + tx := predicate.NewPredicateTx( chainID, nonce, &warp.Module.Address, diff --git a/utils/bytes.go b/utils/bytes.go index 186e3c41ef..54258b20f4 100644 --- a/utils/bytes.go +++ b/utils/bytes.go @@ -3,6 +3,8 @@ package utils +import "github.com/ethereum/go-ethereum/common" + // IncrOne increments bytes value by one func IncrOne(bytes []byte) { index := len(bytes) - 1 @@ -16,3 +18,27 @@ func IncrOne(bytes []byte) { } } } + +// HashSliceToBytes serializes a []common.Hash into a tightly packed byte array. +func HashSliceToBytes(hashes []common.Hash) []byte { + bytes := make([]byte, common.HashLength*len(hashes)) + for i, hash := range hashes { + copy(bytes[i*common.HashLength:], hash[:]) + } + return bytes +} + +// BytesToHashSlice packs [b] into a slice of hash values with zero padding +// to the right if the length of b is not a multiple of 32. +func BytesToHashSlice(b []byte) []common.Hash { + var ( + numHashes = (len(b) + 31) / 32 + hashes = make([]common.Hash, numHashes) + ) + + for i := range hashes { + start := i * common.HashLength + copy(hashes[i][:], b[start:]) + } + return hashes +} diff --git a/utils/bytes_test.go b/utils/bytes_test.go index 121410c85a..b1bbc8fa6b 100644 --- a/utils/bytes_test.go +++ b/utils/bytes_test.go @@ -4,10 +4,13 @@ package utils import ( + "bytes" "testing" + "github.com/ava-labs/avalanchego/utils" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestIncrOne(t *testing.T) { @@ -36,3 +39,28 @@ func TestIncrOne(t *testing.T) { }) } } + +func testBytesToHashSlice(t testing.TB, b []byte) { + hashSlice := BytesToHashSlice(b) + + copiedBytes := HashSliceToBytes(hashSlice) + + if len(b)%32 == 0 { + require.Equal(t, b, copiedBytes) + } else { + require.Equal(t, b, copiedBytes[:len(b)]) + // Require that any additional padding is all zeroes + padding := copiedBytes[len(b):] + require.Equal(t, bytes.Repeat([]byte{0x00}, len(padding)), padding) + } +} + +func FuzzHashSliceToBytes(f *testing.F) { + for i := 0; i < 100; i++ { + f.Add(utils.RandomBytes(i)) + } + + f.Fuzz(func(t *testing.T, b []byte) { + testBytesToHashSlice(t, b) + }) +} diff --git a/utils/predicate/README.md b/utils/predicate/README.md deleted file mode 100644 index 34d8f02aae..0000000000 --- a/utils/predicate/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Predicate Utils - -This package provides simple helpers to pack/unpack byte slices for a predicate transaction, where a byte slice of size N is encoded in the access list of a transaction. - -## Encoding - -A byte slice of size N is encoded as: - -1. Slice of N bytes -2. Delimiter byte `0xff` -3. Appended 0s to the nearest multiple of 32 bytes diff --git a/x/warp/config.go b/x/warp/config.go index 4fe982ada9..b76b97095b 100644 --- a/x/warp/config.go +++ b/x/warp/config.go @@ -12,7 +12,7 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - predicateutils "github.com/ava-labs/subnet-evm/utils/predicate" + "github.com/ava-labs/subnet-evm/predicate" warpValidators "github.com/ava-labs/subnet-evm/warp/validators" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" @@ -158,7 +158,7 @@ func (c *Config) PredicateGas(predicateBytes []byte) (uint64, error) { return 0, fmt.Errorf("overflow adding bytes gas cost of size %d", len(predicateBytes)) } - unpackedPredicateBytes, err := predicateutils.UnpackPredicate(predicateBytes) + unpackedPredicateBytes, err := predicate.UnpackPredicate(predicateBytes) if err != nil { return 0, fmt.Errorf("%w: %s", errInvalidPredicateBytes, err) } @@ -184,7 +184,7 @@ func (c *Config) PredicateGas(predicateBytes []byte) (uint64, error) { } func (c *Config) verifyPredicate(predicateContext *precompileconfig.PredicateContext, predicateBytes []byte) bool { - unpackedPredicateBytes, err := predicateutils.UnpackPredicate(predicateBytes) + unpackedPredicateBytes, err := predicate.UnpackPredicate(predicateBytes) if err != nil { return false } diff --git a/x/warp/contract_test.go b/x/warp/contract_test.go index 3ba6b0f670..e8d763159f 100644 --- a/x/warp/contract_test.go +++ b/x/warp/contract_test.go @@ -16,7 +16,7 @@ import ( "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/precompile/testutils" - predicateutils "github.com/ava-labs/subnet-evm/utils/predicate" + "github.com/ava-labs/subnet-evm/predicate" "github.com/ava-labs/subnet-evm/vmerrs" warpPayload "github.com/ava-labs/subnet-evm/warp/payload" "github.com/ethereum/go-ethereum/common" @@ -174,7 +174,7 @@ func TestGetVerifiedWarpMessage(t *testing.T) { require.NoError(t, err) warpMessage, err := avalancheWarp.NewMessage(unsignedWarpMsg, &avalancheWarp.BitSetSignature{}) // Create message with empty signature for testing require.NoError(t, err) - warpMessagePredicateBytes := predicateutils.PackPredicate(warpMessage.Bytes()) + warpMessagePredicateBytes := predicate.PackPredicate(warpMessage.Bytes()) getVerifiedWarpMsg, err := PackGetVerifiedWarpMessage(0) require.NoError(t, err) @@ -242,7 +242,7 @@ func TestGetVerifiedWarpMessage(t *testing.T) { return input }, BeforeHook: func(t testing.TB, state contract.StateDB) { - state.SetPredicateStorageSlots(ContractAddress, [][]byte{[]byte{}, warpMessagePredicateBytes}) + state.SetPredicateStorageSlots(ContractAddress, [][]byte{{}, warpMessagePredicateBytes}) }, SetupBlockContext: func(mbc *contract.MockBlockContext) { mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(1).Bytes()) @@ -366,7 +366,7 @@ func TestGetVerifiedWarpMessage(t *testing.T) { Caller: callerAddr, InputFn: func(t testing.TB) []byte { return getVerifiedWarpMsg }, BeforeHook: func(t testing.TB, state contract.StateDB) { - state.SetPredicateStorageSlots(ContractAddress, [][]byte{predicateutils.PackPredicate([]byte{1, 2, 3})}) + state.SetPredicateStorageSlots(ContractAddress, [][]byte{predicate.PackPredicate([]byte{1, 2, 3})}) }, SetupBlockContext: func(mbc *contract.MockBlockContext) { mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(0).Bytes()) @@ -384,7 +384,7 @@ func TestGetVerifiedWarpMessage(t *testing.T) { warpMessage, err := avalancheWarp.NewMessage(unsignedMessage, &avalancheWarp.BitSetSignature{}) require.NoError(t, err) - state.SetPredicateStorageSlots(ContractAddress, [][]byte{predicateutils.PackPredicate(warpMessage.Bytes())}) + state.SetPredicateStorageSlots(ContractAddress, [][]byte{predicate.PackPredicate(warpMessage.Bytes())}) }, SetupBlockContext: func(mbc *contract.MockBlockContext) { mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(0).Bytes()) @@ -444,7 +444,7 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { require.NoError(t, err) warpMessage, err := avalancheWarp.NewMessage(unsignedWarpMsg, &avalancheWarp.BitSetSignature{}) // Create message with empty signature for testing require.NoError(t, err) - warpMessagePredicateBytes := predicateutils.PackPredicate(warpMessage.Bytes()) + warpMessagePredicateBytes := predicate.PackPredicate(warpMessage.Bytes()) getVerifiedWarpBlockHash, err := PackGetVerifiedWarpBlockHash(0) require.NoError(t, err) @@ -509,7 +509,7 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { return input }, BeforeHook: func(t testing.TB, state contract.StateDB) { - state.SetPredicateStorageSlots(ContractAddress, [][]byte{[]byte{}, warpMessagePredicateBytes}) + state.SetPredicateStorageSlots(ContractAddress, [][]byte{{}, warpMessagePredicateBytes}) }, SetupBlockContext: func(mbc *contract.MockBlockContext) { mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(1).Bytes()) @@ -627,7 +627,7 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { Caller: callerAddr, InputFn: func(t testing.TB) []byte { return getVerifiedWarpBlockHash }, BeforeHook: func(t testing.TB, state contract.StateDB) { - state.SetPredicateStorageSlots(ContractAddress, [][]byte{predicateutils.PackPredicate([]byte{1, 2, 3})}) + state.SetPredicateStorageSlots(ContractAddress, [][]byte{predicate.PackPredicate([]byte{1, 2, 3})}) }, SetupBlockContext: func(mbc *contract.MockBlockContext) { mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(0).Bytes()) @@ -645,7 +645,7 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { warpMessage, err := avalancheWarp.NewMessage(unsignedMessage, &avalancheWarp.BitSetSignature{}) require.NoError(t, err) - state.SetPredicateStorageSlots(ContractAddress, [][]byte{predicateutils.PackPredicate(warpMessage.Bytes())}) + state.SetPredicateStorageSlots(ContractAddress, [][]byte{predicate.PackPredicate(warpMessage.Bytes())}) }, SetupBlockContext: func(mbc *contract.MockBlockContext) { mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(0).Bytes()) diff --git a/x/warp/contract_warp_handler.go b/x/warp/contract_warp_handler.go index 7348f5c1fc..e2dd907fba 100644 --- a/x/warp/contract_warp_handler.go +++ b/x/warp/contract_warp_handler.go @@ -9,7 +9,7 @@ import ( "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/subnet-evm/precompile/contract" - predicateutils "github.com/ava-labs/subnet-evm/utils/predicate" + "github.com/ava-labs/subnet-evm/predicate" "github.com/ava-labs/subnet-evm/vmerrs" warpPayload "github.com/ava-labs/subnet-evm/warp/payload" "github.com/ethereum/go-ethereum/common" @@ -78,7 +78,7 @@ func handleWarpMessage(accessibleState contract.AccessibleState, input []byte, s } // Note: since the predicate is verified in advance of execution, the precompile should not // hit an error during execution. - unpackedPredicateBytes, err := predicateutils.UnpackPredicate(predicateBytes) + unpackedPredicateBytes, err := predicate.UnpackPredicate(predicateBytes) if err != nil { return nil, remainingGas, fmt.Errorf("%w: %s", errInvalidPredicateBytes, err) } diff --git a/x/warp/predicate_test.go b/x/warp/predicate_test.go index 96b0079fc1..fbb468091f 100644 --- a/x/warp/predicate_test.go +++ b/x/warp/predicate_test.go @@ -21,8 +21,8 @@ import ( "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ava-labs/subnet-evm/precompile/testutils" + "github.com/ava-labs/subnet-evm/predicate" subnetEVMUtils "github.com/ava-labs/subnet-evm/utils" - predicateutils "github.com/ava-labs/subnet-evm/utils/predicate" warpPayload "github.com/ava-labs/subnet-evm/warp/payload" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" @@ -170,7 +170,7 @@ func createWarpMessage(numKeys int) *avalancheWarp.Message { // and packs it into predicate encoding. func createPredicate(numKeys int) []byte { warpMsg := createWarpMessage(numKeys) - predicateBytes := predicateutils.PackPredicate(warpMsg.Bytes()) + predicateBytes := predicate.PackPredicate(warpMsg.Bytes()) return predicateBytes } @@ -261,7 +261,7 @@ func TestWarpMessageFromPrimaryNetwork(t *testing.T) { warpMsg, err := avalancheWarp.NewMessage(unsignedMsg, warpSignature) require.NoError(err) - predicateBytes := predicateutils.PackPredicate(warpMsg.Bytes()) + predicateBytes := predicate.PackPredicate(warpMsg.Bytes()) snowCtx := snow.DefaultContextTest() snowCtx.SubnetID = ids.GenerateTestID() @@ -338,7 +338,7 @@ func TestInvalidWarpMessage(t *testing.T) { warpMsg := createWarpMessage(1) warpMsgBytes := warpMsg.Bytes() warpMsgBytes = append(warpMsgBytes, byte(0x01)) // Invalidate warp message packing - predicateBytes := predicateutils.PackPredicate(warpMsgBytes) + predicateBytes := predicate.PackPredicate(warpMsgBytes) test := testutils.PredicateTest{ Config: NewDefaultConfig(subnetEVMUtils.NewUint64(0)), @@ -383,7 +383,7 @@ func TestInvalidAddressedPayload(t *testing.T) { warpMsg, err := avalancheWarp.NewMessage(unsignedMsg, warpSignature) require.NoError(t, err) warpMsgBytes := warpMsg.Bytes() - predicateBytes := predicateutils.PackPredicate(warpMsgBytes) + predicateBytes := predicate.PackPredicate(warpMsgBytes) test := testutils.PredicateTest{ Config: NewDefaultConfig(subnetEVMUtils.NewUint64(0)), @@ -428,7 +428,7 @@ func TestInvalidBitSet(t *testing.T) { publicKey: true, }, }) - predicateBytes := predicateutils.PackPredicate(msg.Bytes()) + predicateBytes := predicate.PackPredicate(msg.Bytes()) test := testutils.PredicateTest{ Config: NewDefaultConfig(subnetEVMUtils.NewUint64(0)), PredicateContext: &precompileconfig.PredicateContext{ From 74e32f6b8e0cd1fd8fbec183d547324379089011 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Tue, 3 Oct 2023 11:48:17 -0400 Subject: [PATCH 25/52] Update release checklist to include deploying to WAGMI (#931) --- .github/ISSUE_TEMPLATE/release_checklist.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md index 66661c7a9f..f71bab419f 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -26,4 +26,5 @@ Link the relevant documentation PRs for this release. - [ ] Update AvalancheGo dependency in scripts/versions.sh for e2e tests. - [ ] Add new entry in compatibility.json for RPCChainVM Compatibility - [ ] Update AvalancheGo compatibility in README +- [ ] Deploy to WAGMI - [ ] Confirm goreleaser job has successfully generated binaries by checking the releases page From a2a42fa95c36431c049d22aaf326749a22769372 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 3 Oct 2023 11:45:58 -0700 Subject: [PATCH 26/52] bump golangci-lint (#927) * bump golangci-lint * undo wspace change --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ac7f92a04..35f03e39d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: - version: v1.51 + version: v1.54 working-directory: . args: --timeout 10m From 91fd585c44e744cb33e2493969f548705df84aa2 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 3 Oct 2023 21:49:29 +0300 Subject: [PATCH 27/52] mark flaky test (#917) --- plugin/evm/syncervm_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/evm/syncervm_test.go b/plugin/evm/syncervm_test.go index abade7083c..b764d5b60d 100644 --- a/plugin/evm/syncervm_test.go +++ b/plugin/evm/syncervm_test.go @@ -230,6 +230,7 @@ func TestStateSyncToggleEnabledToDisabled(t *testing.T) { } func TestVMShutdownWhileSyncing(t *testing.T) { + t.Skip("FLAKY") var ( lock sync.Mutex vmSetup *syncVMSetup From f8bf34810ecd187bff2779f31cb5fb80eaf3055c Mon Sep 17 00:00:00 2001 From: Cesar <137245636+nytzuga@users.noreply.github.com> Date: Tue, 3 Oct 2023 16:54:13 -0300 Subject: [PATCH 28/52] Remove destinationAddress and destinationChainID from Warp Message (#920) * Remove destinationAddress and destinationChainID from Warp Message * Use UnpackInput instead of UnpackInputIntoInterface The generated code now matches the output of generate_procompile.sh script --- contracts/contracts/ExampleWarp.sol | 30 +++----------- .../contracts/interfaces/IWarpMessenger.sol | 25 +++--------- plugin/evm/ExampleWarp.abi | 20 ---------- plugin/evm/ExampleWarp.bin | 2 +- plugin/evm/vm_warp_test.go | 22 +--------- tests/warp/warp_test.go | 8 +--- warp/payload/README.md | 8 +--- warp/payload/addressed_payload.go | 14 +++---- warp/payload/payload_test.go | 12 ++---- x/warp/README.md | 6 --- x/warp/contract.abi | 34 +--------------- x/warp/contract.go | 40 +++++++------------ x/warp/contract_test.go | 19 +-------- x/warp/contract_warp_handler.go | 2 - x/warp/predicate_test.go | 11 ++--- 15 files changed, 45 insertions(+), 208 deletions(-) diff --git a/contracts/contracts/ExampleWarp.sol b/contracts/contracts/ExampleWarp.sol index 726b00c1c4..8701681fd9 100644 --- a/contracts/contracts/ExampleWarp.sol +++ b/contracts/contracts/ExampleWarp.sol @@ -8,13 +8,9 @@ contract ExampleWarp { address constant WARP_ADDRESS = 0x0200000000000000000000000000000000000005; IWarpMessenger warp = IWarpMessenger(WARP_ADDRESS); - // sendWarpMessage sends a warp message to the specified destination chain and address pair containing the payload - function sendWarpMessage( - bytes32 destinationChainID, - address destinationAddress, - bytes calldata payload - ) external { - warp.sendWarpMessage(destinationChainID, destinationAddress, payload); + // sendWarpMessage sends a warp message containing the payload + function sendWarpMessage(bytes calldata payload) external { + warp.sendWarpMessage(payload); } // validateWarpMessage retrieves the warp message attached to the transaction and verifies all of its attributes. @@ -22,47 +18,33 @@ contract ExampleWarp { uint32 index, bytes32 sourceChainID, address originSenderAddress, - bytes32 destinationChainID, - address destinationAddress, bytes calldata payload ) external view { (WarpMessage memory message, bool valid) = warp.getVerifiedWarpMessage(index); require(valid); require(message.sourceChainID == sourceChainID); require(message.originSenderAddress == originSenderAddress); - require(message.destinationChainID == destinationChainID); - require(message.destinationAddress == destinationAddress); require(keccak256(message.payload) == keccak256(payload)); } - function validateInvalidWarpMessage( - uint32 index - ) external view { + function validateInvalidWarpMessage(uint32 index) external view { (WarpMessage memory message, bool valid) = warp.getVerifiedWarpMessage(index); require(!valid); require(message.sourceChainID == bytes32(0)); require(message.originSenderAddress == address(0)); - require(message.destinationChainID == bytes32(0)); - require(message.destinationAddress == address(0)); require(keccak256(message.payload) == keccak256(bytes(""))); } // validateWarpBlockHash retrieves the warp block hash attached to the transaction and verifies it matches the // expected block hash. - function validateWarpBlockHash( - uint32 index, - bytes32 sourceChainID, - bytes32 blockHash - ) external view { + function validateWarpBlockHash(uint32 index, bytes32 sourceChainID, bytes32 blockHash) external view { (WarpBlockHash memory warpBlockHash, bool valid) = warp.getVerifiedWarpBlockHash(index); require(valid); require(warpBlockHash.sourceChainID == sourceChainID); require(warpBlockHash.blockHash == blockHash); } - function validateInvalidWarpBlockHash( - uint32 index - ) external view { + function validateInvalidWarpBlockHash(uint32 index) external view { (WarpBlockHash memory warpBlockHash, bool valid) = warp.getVerifiedWarpBlockHash(index); require(!valid); require(warpBlockHash.sourceChainID == bytes32(0)); diff --git a/contracts/contracts/interfaces/IWarpMessenger.sol b/contracts/contracts/interfaces/IWarpMessenger.sol index 7cf925ee35..5c67e0d1d4 100644 --- a/contracts/contracts/interfaces/IWarpMessenger.sol +++ b/contracts/contracts/interfaces/IWarpMessenger.sol @@ -8,8 +8,6 @@ pragma solidity ^0.8.0; struct WarpMessage { bytes32 sourceChainID; address originSenderAddress; - bytes32 destinationChainID; - address destinationAddress; bytes payload; } @@ -19,12 +17,7 @@ struct WarpBlockHash { } interface IWarpMessenger { - event SendWarpMessage( - bytes32 indexed destinationChainID, - address indexed destinationAddress, - address indexed sender, - bytes message - ); + event SendWarpMessage(address indexed sender, bytes message); // sendWarpMessage emits a request for the subnet to send a warp message from [msg.sender] // with the specified parameters. @@ -33,29 +26,23 @@ interface IWarpMessenger { // precompile. // Each validator then adds the UnsignedWarpMessage encoded in the log to the set of messages // it is willing to sign for an off-chain relayer to aggregate Warp signatures. - function sendWarpMessage( - bytes32 destinationChainID, - address destinationAddress, - bytes calldata payload - ) external; + function sendWarpMessage(bytes calldata payload) external; // getVerifiedWarpMessage parses the pre-verified warp message in the // predicate storage slots as a WarpMessage and returns it to the caller. // If the message exists and passes verification, returns the verified message // and true. // Otherwise, returns false and the empty value for the message. - function getVerifiedWarpMessage(uint32 index) - external view - returns (WarpMessage calldata message, bool valid); + function getVerifiedWarpMessage(uint32 index) external view returns (WarpMessage calldata message, bool valid); // getVerifiedWarpBlockHash parses the pre-verified WarpBlockHash message in the // predicate storage slots as a WarpBlockHash message and returns it to the caller. // If the message exists and passes verification, returns the verified message // and true. // Otherwise, returns false and the empty value for the message. - function getVerifiedWarpBlockHash(uint32 index) - external view - returns (WarpBlockHash calldata warpBlockHash, bool valid); + function getVerifiedWarpBlockHash( + uint32 index + ) external view returns (WarpBlockHash calldata warpBlockHash, bool valid); // getBlockchainID returns the snow.Context BlockchainID of this chain. // This blockchainID is the hash of the transaction that created this blockchain on the P-Chain diff --git a/plugin/evm/ExampleWarp.abi b/plugin/evm/ExampleWarp.abi index a45ea8088b..9d4b442caa 100644 --- a/plugin/evm/ExampleWarp.abi +++ b/plugin/evm/ExampleWarp.abi @@ -1,16 +1,6 @@ [ { "inputs": [ - { - "internalType": "bytes32", - "name": "destinationChainID", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "destinationAddress", - "type": "address" - }, { "internalType": "bytes", "name": "payload", @@ -101,16 +91,6 @@ "name": "originSenderAddress", "type": "address" }, - { - "internalType": "bytes32", - "name": "destinationChainID", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "destinationAddress", - "type": "address" - }, { "internalType": "bytes", "name": "payload", diff --git a/plugin/evm/ExampleWarp.bin b/plugin/evm/ExampleWarp.bin index 6aa001f4d4..c5963ac7ea 100644 --- a/plugin/evm/ExampleWarp.bin +++ b/plugin/evm/ExampleWarp.bin @@ -1 +1 @@ -60806040527302000000000000000000000000000000000000056000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561006457600080fd5b50610eef806100746000396000f3fe608060405234801561001057600080fd5b50600436106100625760003560e01c806315f0c9591461006757806324b9c5a2146100835780636f57e4c41461009f57806377ca84db146100bb578063e519286f146100d7578063f25ec06a146100f3575b600080fd5b610081600480360381019061007c919061073a565b61010f565b005b61009d6004803603810190610098919061082a565b6101ac565b005b6100b960048036038101906100b491906108da565b610243565b005b6100d560048036038101906100d09190610989565b6103c4565b005b6100f160048036038101906100ec91906109b6565b61049b565b005b61010d60048036038101906101089190610989565b61056d565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16634213cf786040518163ffffffff1660e01b8152600401602060405180830381865afa15801561017a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061019e9190610a1e565b81146101a957600080fd5b50565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166324b9c5a2858585856040518563ffffffff1660e01b815260040161020b9493929190610ac7565b600060405180830381600087803b15801561022557600080fd5b505af1158015610239573d6000803e3d6000fd5b5050505050505050565b60008060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636f8253508a6040518263ffffffff1660e01b815260040161029f9190610b16565b600060405180830381865afa1580156102bc573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052508101906102e59190610d84565b91509150806102f357600080fd5b8782600001511461030357600080fd5b8673ffffffffffffffffffffffffffffffffffffffff16826020015173ffffffffffffffffffffffffffffffffffffffff161461033f57600080fd5b8582604001511461034f57600080fd5b8473ffffffffffffffffffffffffffffffffffffffff16826060015173ffffffffffffffffffffffffffffffffffffffff161461038b57600080fd5b838360405161039b929190610e10565b6040518091039020826080015180519060200120146103b957600080fd5b505050505050505050565b60008060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663ce7f5929846040518263ffffffff1660e01b81526004016104209190610b16565b606060405180830381865afa15801561043d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104619190610e79565b91509150801561047057600080fd5b6000801b82600001511461048357600080fd5b6000801b82602001511461049657600080fd5b505050565b60008060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663ce7f5929866040518263ffffffff1660e01b81526004016104f79190610b16565b606060405180830381865afa158015610514573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105389190610e79565b915091508061054657600080fd5b8382600001511461055657600080fd5b8282602001511461056657600080fd5b5050505050565b60008060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636f825350846040518263ffffffff1660e01b81526004016105c99190610b16565b600060405180830381865afa1580156105e6573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f8201168201806040525081019061060f9190610d84565b91509150801561061e57600080fd5b6000801b82600001511461063157600080fd5b600073ffffffffffffffffffffffffffffffffffffffff16826020015173ffffffffffffffffffffffffffffffffffffffff161461066e57600080fd5b6000801b82604001511461068157600080fd5b600073ffffffffffffffffffffffffffffffffffffffff16826060015173ffffffffffffffffffffffffffffffffffffffff16146106be57600080fd5b6040518060200160405280600081525080519060200120826080015180519060200120146106eb57600080fd5b505050565b6000604051905090565b600080fd5b600080fd5b6000819050919050565b61071781610704565b811461072257600080fd5b50565b6000813590506107348161070e565b92915050565b6000602082840312156107505761074f6106fa565b5b600061075e84828501610725565b91505092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061079282610767565b9050919050565b6107a281610787565b81146107ad57600080fd5b50565b6000813590506107bf81610799565b92915050565b600080fd5b600080fd5b600080fd5b60008083601f8401126107ea576107e96107c5565b5b8235905067ffffffffffffffff811115610807576108066107ca565b5b602083019150836001820283011115610823576108226107cf565b5b9250929050565b60008060008060608587031215610844576108436106fa565b5b600061085287828801610725565b9450506020610863878288016107b0565b935050604085013567ffffffffffffffff811115610884576108836106ff565b5b610890878288016107d4565b925092505092959194509250565b600063ffffffff82169050919050565b6108b78161089e565b81146108c257600080fd5b50565b6000813590506108d4816108ae565b92915050565b600080600080600080600060c0888a0312156108f9576108f86106fa565b5b60006109078a828b016108c5565b97505060206109188a828b01610725565b96505060406109298a828b016107b0565b955050606061093a8a828b01610725565b945050608061094b8a828b016107b0565b93505060a088013567ffffffffffffffff81111561096c5761096b6106ff565b5b6109788a828b016107d4565b925092505092959891949750929550565b60006020828403121561099f5761099e6106fa565b5b60006109ad848285016108c5565b91505092915050565b6000806000606084860312156109cf576109ce6106fa565b5b60006109dd868287016108c5565b93505060206109ee86828701610725565b92505060406109ff86828701610725565b9150509250925092565b600081519050610a188161070e565b92915050565b600060208284031215610a3457610a336106fa565b5b6000610a4284828501610a09565b91505092915050565b610a5481610704565b82525050565b610a6381610787565b82525050565b600082825260208201905092915050565b82818337600083830152505050565b6000601f19601f8301169050919050565b6000610aa68385610a69565b9350610ab3838584610a7a565b610abc83610a89565b840190509392505050565b6000606082019050610adc6000830187610a4b565b610ae96020830186610a5a565b8181036040830152610afc818486610a9a565b905095945050505050565b610b108161089e565b82525050565b6000602082019050610b2b6000830184610b07565b92915050565b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b610b6e82610a89565b810181811067ffffffffffffffff82111715610b8d57610b8c610b36565b5b80604052505050565b6000610ba06106f0565b9050610bac8282610b65565b919050565b600080fd5b600081519050610bc581610799565b92915050565b600080fd5b600067ffffffffffffffff821115610beb57610bea610b36565b5b610bf482610a89565b9050602081019050919050565b60005b83811015610c1f578082015181840152602081019050610c04565b83811115610c2e576000848401525b50505050565b6000610c47610c4284610bd0565b610b96565b905082815260208101848484011115610c6357610c62610bcb565b5b610c6e848285610c01565b509392505050565b600082601f830112610c8b57610c8a6107c5565b5b8151610c9b848260208601610c34565b91505092915050565b600060a08284031215610cba57610cb9610b31565b5b610cc460a0610b96565b90506000610cd484828501610a09565b6000830152506020610ce884828501610bb6565b6020830152506040610cfc84828501610a09565b6040830152506060610d1084828501610bb6565b606083015250608082015167ffffffffffffffff811115610d3457610d33610bb1565b5b610d4084828501610c76565b60808301525092915050565b60008115159050919050565b610d6181610d4c565b8114610d6c57600080fd5b50565b600081519050610d7e81610d58565b92915050565b60008060408385031215610d9b57610d9a6106fa565b5b600083015167ffffffffffffffff811115610db957610db86106ff565b5b610dc585828601610ca4565b9250506020610dd685828601610d6f565b9150509250929050565b600081905092915050565b6000610df78385610de0565b9350610e04838584610a7a565b82840190509392505050565b6000610e1d828486610deb565b91508190509392505050565b600060408284031215610e3f57610e3e610b31565b5b610e496040610b96565b90506000610e5984828501610a09565b6000830152506020610e6d84828501610a09565b60208301525092915050565b60008060608385031215610e9057610e8f6106fa565b5b6000610e9e85828601610e29565b9250506040610eaf85828601610d6f565b915050925092905056fea2646970667358221220fc88d6f0cab27cb1721b87381fb14f8f41246682162ff9bd7d2d6a6a46dcf29264736f6c634300080d0033 \ No newline at end of file +60806040527302000000000000000000000000000000000000055f806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550348015610062575f80fd5b50610d15806100705f395ff3fe608060405234801561000f575f80fd5b5060043610610060575f3560e01c806315f0c959146100645780635bd05f061461008057806377ca84db1461009c578063e519286f146100b8578063ee5b48eb146100d4578063f25ec06a146100f0575b5f80fd5b61007e60048036038101906100799190610658565b61010c565b005b61009a60048036038101906100959190610777565b6101a5565b005b6100b660048036038101906100b191906107fb565b6102cd565b005b6100d260048036038101906100cd9190610826565b61039a565b005b6100ee60048036038101906100e99190610876565b610464565b005b61010a600480360381019061010591906107fb565b6104ef565b005b5f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16634213cf786040518163ffffffff1660e01b8152600401602060405180830381865afa158015610174573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061019891906108d5565b81146101a2575f80fd5b50565b5f805f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636f825350886040518263ffffffff1660e01b81526004016101ff919061090f565b5f60405180830381865afa158015610219573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906102419190610b48565b915091508061024e575f80fd5b85825f01511461025c575f80fd5b8473ffffffffffffffffffffffffffffffffffffffff16826020015173ffffffffffffffffffffffffffffffffffffffff1614610297575f80fd5b83836040516102a7929190610bde565b6040518091039020826040015180519060200120146102c4575f80fd5b50505050505050565b5f805f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663ce7f5929846040518263ffffffff1660e01b8152600401610327919061090f565b606060405180830381865afa158015610342573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103669190610c43565b915091508015610374575f80fd5b5f801b825f015114610384575f80fd5b5f801b826020015114610395575f80fd5b505050565b5f805f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663ce7f5929866040518263ffffffff1660e01b81526004016103f4919061090f565b606060405180830381865afa15801561040f573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104339190610c43565b9150915080610440575f80fd5b83825f01511461044e575f80fd5b8282602001511461045d575f80fd5b5050505050565b5f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663ee5b48eb83836040518363ffffffff1660e01b81526004016104be929190610cbd565b5f604051808303815f87803b1580156104d5575f80fd5b505af11580156104e7573d5f803e3d5ffd5b505050505050565b5f805f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636f825350846040518263ffffffff1660e01b8152600401610549919061090f565b5f60405180830381865afa158015610563573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061058b9190610b48565b915091508015610599575f80fd5b5f801b825f0151146105a9575f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff16826020015173ffffffffffffffffffffffffffffffffffffffff16146105e4575f80fd5b60405180602001604052805f815250805190602001208260400151805190602001201461060f575f80fd5b505050565b5f604051905090565b5f80fd5b5f80fd5b5f819050919050565b61063781610625565b8114610641575f80fd5b50565b5f813590506106528161062e565b92915050565b5f6020828403121561066d5761066c61061d565b5b5f61067a84828501610644565b91505092915050565b5f63ffffffff82169050919050565b61069b81610683565b81146106a5575f80fd5b50565b5f813590506106b681610692565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6106e5826106bc565b9050919050565b6106f5816106db565b81146106ff575f80fd5b50565b5f81359050610710816106ec565b92915050565b5f80fd5b5f80fd5b5f80fd5b5f8083601f84011261073757610736610716565b5b8235905067ffffffffffffffff8111156107545761075361071a565b5b6020830191508360018202830111156107705761076f61071e565b5b9250929050565b5f805f805f608086880312156107905761078f61061d565b5b5f61079d888289016106a8565b95505060206107ae88828901610644565b94505060406107bf88828901610702565b935050606086013567ffffffffffffffff8111156107e0576107df610621565b5b6107ec88828901610722565b92509250509295509295909350565b5f602082840312156108105761080f61061d565b5b5f61081d848285016106a8565b91505092915050565b5f805f6060848603121561083d5761083c61061d565b5b5f61084a868287016106a8565b935050602061085b86828701610644565b925050604061086c86828701610644565b9150509250925092565b5f806020838503121561088c5761088b61061d565b5b5f83013567ffffffffffffffff8111156108a9576108a8610621565b5b6108b585828601610722565b92509250509250929050565b5f815190506108cf8161062e565b92915050565b5f602082840312156108ea576108e961061d565b5b5f6108f7848285016108c1565b91505092915050565b61090981610683565b82525050565b5f6020820190506109225f830184610900565b92915050565b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6109728261092c565b810181811067ffffffffffffffff821117156109915761099061093c565b5b80604052505050565b5f6109a3610614565b90506109af8282610969565b919050565b5f80fd5b5f815190506109c6816106ec565b92915050565b5f80fd5b5f67ffffffffffffffff8211156109ea576109e961093c565b5b6109f38261092c565b9050602081019050919050565b5f5b83811015610a1d578082015181840152602081019050610a02565b5f8484015250505050565b5f610a3a610a35846109d0565b61099a565b905082815260208101848484011115610a5657610a556109cc565b5b610a61848285610a00565b509392505050565b5f82601f830112610a7d57610a7c610716565b5b8151610a8d848260208601610a28565b91505092915050565b5f60608284031215610aab57610aaa610928565b5b610ab5606061099a565b90505f610ac4848285016108c1565b5f830152506020610ad7848285016109b8565b602083015250604082015167ffffffffffffffff811115610afb57610afa6109b4565b5b610b0784828501610a69565b60408301525092915050565b5f8115159050919050565b610b2781610b13565b8114610b31575f80fd5b50565b5f81519050610b4281610b1e565b92915050565b5f8060408385031215610b5e57610b5d61061d565b5b5f83015167ffffffffffffffff811115610b7b57610b7a610621565b5b610b8785828601610a96565b9250506020610b9885828601610b34565b9150509250929050565b5f81905092915050565b828183375f83830152505050565b5f610bc58385610ba2565b9350610bd2838584610bac565b82840190509392505050565b5f610bea828486610bba565b91508190509392505050565b5f60408284031215610c0b57610c0a610928565b5b610c15604061099a565b90505f610c24848285016108c1565b5f830152506020610c37848285016108c1565b60208301525092915050565b5f8060608385031215610c5957610c5861061d565b5b5f610c6685828601610bf6565b9250506040610c7785828601610b34565b9150509250929050565b5f82825260208201905092915050565b5f610c9c8385610c81565b9350610ca9838584610bac565b610cb28361092c565b840190509392505050565b5f6020820190508181035f830152610cd6818486610c91565b9050939250505056fea2646970667358221220d2f09e48f2e77361389456025f7337767127dc73767d50ff2f46bc5273493cec64736f6c63430008150033 \ No newline at end of file diff --git a/plugin/evm/vm_warp_test.go b/plugin/evm/vm_warp_test.go index 3a00d3889f..99542949fb 100644 --- a/plugin/evm/vm_warp_test.go +++ b/plugin/evm/vm_warp_test.go @@ -64,11 +64,7 @@ func TestSendWarpMessage(t *testing.T) { payload := utils.RandomBytes(100) - warpSendMessageInput, err := warp.PackSendWarpMessage(warp.SendWarpMessageInput{ - DestinationChainID: common.Hash(vm.ctx.CChainID), - DestinationAddress: testEthAddrs[1], - Payload: payload, - }) + warpSendMessageInput, err := warp.PackSendWarpMessage(payload) require.NoError(err) // Submit a transaction to trigger sending a warp message @@ -96,8 +92,6 @@ func TestSendWarpMessage(t *testing.T) { require.Len(receipts[0].Logs, 1) expectedTopics := []common.Hash{ warp.WarpABI.Events["SendWarpMessage"].ID, - common.Hash(vm.ctx.CChainID), - testEthAddrs[1].Hash(), testEthAddrs[0].Hash(), } require.Equal(expectedTopics, receipts[0].Logs[0].Topics) @@ -134,13 +128,9 @@ func TestValidateWarpMessage(t *testing.T) { require := require.New(t) sourceChainID := ids.GenerateTestID() sourceAddress := common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2") - destinationChainID := ids.GenerateTestID() - destinationAddress := common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87") payload := []byte{1, 2, 3} addressedPayload, err := warpPayload.NewAddressedPayload( sourceAddress, - common.Hash(destinationChainID), - destinationAddress, payload, ) require.NoError(err) @@ -153,8 +143,6 @@ func TestValidateWarpMessage(t *testing.T) { uint32(0), sourceChainID, sourceAddress, - destinationChainID, - destinationAddress, payload, ) require.NoError(err) @@ -166,13 +154,9 @@ func TestValidateInvalidWarpMessage(t *testing.T) { require := require.New(t) sourceChainID := ids.GenerateTestID() sourceAddress := common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2") - destinationChainID := ids.GenerateTestID() - destinationAddress := common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87") payload := []byte{1, 2, 3} addressedPayload, err := warpPayload.NewAddressedPayload( sourceAddress, - common.Hash(destinationChainID), - destinationAddress, payload, ) require.NoError(err) @@ -409,8 +393,6 @@ func TestReceiveWarpMessage(t *testing.T) { addressedPayload, err := warpPayload.NewAddressedPayload( testEthAddrs[0], - common.Hash(vm.ctx.CChainID), - testEthAddrs[1], payload, ) require.NoError(err) @@ -549,8 +531,6 @@ func TestReceiveWarpMessage(t *testing.T) { Message: warp.WarpMessage{ SourceChainID: common.Hash(vm.ctx.ChainID), OriginSenderAddress: testEthAddrs[0], - DestinationChainID: common.Hash(vm.ctx.CChainID), - DestinationAddress: testEthAddrs[1], Payload: payload, }, Valid: true, diff --git a/tests/warp/warp_test.go b/tests/warp/warp_test.go index 345825d90d..4b5894c91a 100644 --- a/tests/warp/warp_test.go +++ b/tests/warp/warp_test.go @@ -166,7 +166,7 @@ var _ = ginkgo.Describe("[Warp]", ginkgo.Ordered, func() { subnetB := subnetIDs[1] subnetBDetails, ok := manager.GetSubnet(subnetB) gomega.Expect(ok).Should(gomega.BeTrue()) - blockchainIDB := subnetBDetails.BlockchainID + blockchainIDB = subnetBDetails.BlockchainID gomega.Expect(len(subnetBDetails.ValidatorURIs)).Should(gomega.Equal(5)) chainBURIs = append(chainBURIs, subnetBDetails.ValidatorURIs...) @@ -198,11 +198,7 @@ var _ = ginkgo.Describe("[Warp]", ginkgo.Ordered, func() { startingNonce, err := chainAWSClient.NonceAt(ctx, fundedAddress, nil) gomega.Expect(err).Should(gomega.BeNil()) - packedInput, err := warp.PackSendWarpMessage(warp.SendWarpMessageInput{ - DestinationChainID: common.Hash(blockchainIDB), - DestinationAddress: fundedAddress, - Payload: payload, - }) + packedInput, err := warp.PackSendWarpMessage(payload) gomega.Expect(err).Should(gomega.BeNil()) tx := types.NewTx(&types.DynamicFeeTx{ ChainID: chainID, diff --git a/warp/payload/README.md b/warp/payload/README.md index 71f89c2c6e..1da3b92712 100644 --- a/warp/payload/README.md +++ b/warp/payload/README.md @@ -13,21 +13,15 @@ AddressedPayload: +---------------------+----------+-------------------+ | sourceAddress : [20]byte | 20 bytes | +---------------------+----------+-------------------+ -| destinationChainID : [32]byte | 32 bytes | -+---------------------+----------+-------------------+ -| destinationAddress : [20]byte | 20 bytes | -+---------------------+----------+-------------------+ | payload : []byte | 4 + len(payload) | +---------------------+----------+-------------------+ - | 82 + len(payload) | + | 30 + len(payload) | +-------------------+ ``` - `codecID` is the codec version used to serialize the payload and is hardcoded to `0x0000` - `typeID` is the payload type identifier and is `0x00000000` for `AddressedPayload` - `sourceAddress` is the address that called `sendWarpPrecompile` on the source chain -- `destinationChainID` is the blockchainID that the message is intended for -- `destinationAddress` is the address that should be called on the destination chain - `payload` is an arbitrary byte array payload ## BlockHashPayload diff --git a/warp/payload/addressed_payload.go b/warp/payload/addressed_payload.go index 41afbad847..a0ed6767d6 100644 --- a/warp/payload/addressed_payload.go +++ b/warp/payload/addressed_payload.go @@ -12,21 +12,17 @@ import ( // AddressedPayload defines the format for delivering a point to point message across VMs // ie. (ChainA, AddressA) -> (ChainB, AddressB) type AddressedPayload struct { - SourceAddress common.Address `serialize:"true"` - DestinationChainID common.Hash `serialize:"true"` - DestinationAddress common.Address `serialize:"true"` - Payload []byte `serialize:"true"` + SourceAddress common.Address `serialize:"true"` + Payload []byte `serialize:"true"` bytes []byte } // NewAddressedPayload creates a new *AddressedPayload and initializes it. -func NewAddressedPayload(sourceAddress common.Address, destinationChainID common.Hash, destinationAddress common.Address, payload []byte) (*AddressedPayload, error) { +func NewAddressedPayload(sourceAddress common.Address, payload []byte) (*AddressedPayload, error) { ap := &AddressedPayload{ - SourceAddress: sourceAddress, - DestinationChainID: destinationChainID, - DestinationAddress: destinationAddress, - Payload: payload, + SourceAddress: sourceAddress, + Payload: payload, } return ap, ap.initialize() } diff --git a/warp/payload/payload_test.go b/warp/payload/payload_test.go index 01a590e3e9..69856538cb 100644 --- a/warp/payload/payload_test.go +++ b/warp/payload/payload_test.go @@ -17,8 +17,6 @@ func TestAddressedPayload(t *testing.T) { require := require.New(t) addressedPayload, err := NewAddressedPayload( - common.Address(ids.GenerateTestShortID()), - common.Hash(ids.GenerateTestID()), common.Address(ids.GenerateTestShortID()), []byte{1, 2, 3}, ) @@ -36,12 +34,10 @@ func TestParseAddressedPayloadJunk(t *testing.T) { } func TestParseAddressedPayload(t *testing.T) { - base64Payload := "AAAAAAAAAQIDAAAAAAAAAAAAAAAAAAAAAAAEBQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcICQAAAAAAAAAAAAAAAAAAAAAAAAAAAwoLDA==" + base64Payload := "AAAAAAAAAQIDAAAAAAAAAAAAAAAAAAAAAAAAAAADCgsM" payload := &AddressedPayload{ - SourceAddress: common.Address{1, 2, 3}, - DestinationChainID: common.Hash{4, 5, 6}, - DestinationAddress: common.Address{7, 8, 9}, - Payload: []byte{10, 11, 12}, + SourceAddress: common.Address{1, 2, 3}, + Payload: []byte{10, 11, 12}, } require.NoError(t, payload.initialize()) @@ -91,8 +87,6 @@ func TestParseWrongPayloadType(t *testing.T) { require.NoError(err) addressedPayload, err := NewAddressedPayload( - common.Address(ids.GenerateTestShortID()), - common.Hash(ids.GenerateTestID()), common.Address(ids.GenerateTestShortID()), []byte{1, 2, 3}, ) diff --git a/x/warp/README.md b/x/warp/README.md index 975e2ecd27..846a258650 100644 --- a/x/warp/README.md +++ b/x/warp/README.md @@ -40,8 +40,6 @@ The Warp Precompile is broken down into three functions defined in the Solidity - `SourceChainID` - blockchainID of the sourceChain on the Avalanche P-Chain - `SourceAddress` - `msg.sender` encoded as a 32 byte value that calls `sendWarpMessage` -- `DestinationChainID` - `bytes32` argument specifies the blockchainID on the Avalanche P-Chain that should receive the message -- `DestinationAddress` - 32 byte value that represents the destination address that should receive the message (on the EVM this is the 20 byte address left zero extended) - `Payload` - `payload` argument specified in the call to `sendWarpMessage` emitted as the unindexed data of the resulting log Calling this function will issue a `SendWarpMessage` event from the Warp Precompile. Since the EVM limits the number of topics to 4 including the EventID, this message includes only the topics that would be expected to help filter messages emitted from the Warp Precompile the most. @@ -50,8 +48,6 @@ Specifically, the `payload` is not emitted as a topic because each topic must be Additionally, the `SourceChainID` is excluded because anyone parsing the chain can be expected to already know the blockchainID. Therefore, the `SendWarpMessage` event includes the indexable attributes: -- `destinationChainID` -- `destinationAddress` - `sender` The actual `message` is the entire [Avalanche Warp Unsigned Message](https://github.com/ava-labs/avalanchego/blob/master/vms/platformvm/warp/unsigned_message.go#L14) including the Subnet-EVM [Addressed Payload](../../../warp/payload/payload.go). @@ -72,8 +68,6 @@ This leads to the following advantages: This pre-verification is performed using the ProposerVM Block header during [block verification](../../../plugin/evm/block.go#L220) and [block building](../../../miner/worker.go#L200). -Note: in order to support the notion of an `AnycastID` for the `DestinationChainID`, `getVerifiedMessage` and the predicate DO NOT require that the `DestinationChainID` matches the `blockchainID` currently running. Instead, callers of `getVerifiedMessage` should use `getBlockchainID()` to decide how they should interpret the message. In other words, does the `destinationChainID` match either the local `blockchainID` or the `AnycastID`. - #### getBlockchainID `getBlockchainID` returns the blockchainID of the blockchain that Subnet-EVM is running on. diff --git a/x/warp/contract.abi b/x/warp/contract.abi index 7e83a4de69..04aa755c32 100644 --- a/x/warp/contract.abi +++ b/x/warp/contract.abi @@ -2,18 +2,6 @@ { "anonymous": false, "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "destinationChainID", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "destinationAddress", - "type": "address" - }, { "indexed": true, "internalType": "address", @@ -101,16 +89,6 @@ "name": "originSenderAddress", "type": "address" }, - { - "internalType": "bytes32", - "name": "destinationChainID", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "destinationAddress", - "type": "address" - }, { "internalType": "bytes", "name": "payload", @@ -132,16 +110,6 @@ }, { "inputs": [ - { - "internalType": "bytes32", - "name": "destinationChainID", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "destinationAddress", - "type": "address" - }, { "internalType": "bytes", "name": "payload", @@ -153,4 +121,4 @@ "stateMutability": "nonpayable", "type": "function" } -] \ No newline at end of file +] diff --git a/x/warp/contract.go b/x/warp/contract.go index cb22b40aba..6f076a136e 100644 --- a/x/warp/contract.go +++ b/x/warp/contract.go @@ -68,8 +68,6 @@ type GetVerifiedWarpBlockHashOutput struct { type WarpMessage struct { SourceChainID common.Hash OriginSenderAddress common.Address - DestinationChainID common.Hash - DestinationAddress common.Address Payload []byte } @@ -77,11 +75,6 @@ type GetVerifiedWarpMessageOutput struct { Message WarpMessage Valid bool } -type SendWarpMessageInput struct { - DestinationChainID common.Hash - DestinationAddress common.Address - Payload []byte -} // PackGetBlockchainID packs the include selector (first 4 func signature bytes). // This function is mostly used for tests. @@ -191,18 +184,20 @@ func getVerifiedWarpMessage(accessibleState contract.AccessibleState, caller com return handleWarpMessage(accessibleState, input, suppliedGas, addressedPayloadHandler{}) } -// UnpackSendWarpMessageInput attempts to unpack [input] as SendWarpMessageInput +// UnpackSendWarpMessageInput attempts to unpack [input] as []byte // assumes that [input] does not include selector (omits first 4 func signature bytes) -func UnpackSendWarpMessageInput(input []byte) (SendWarpMessageInput, error) { - inputStruct := SendWarpMessageInput{} - err := WarpABI.UnpackInputIntoInterface(&inputStruct, "sendWarpMessage", input) - - return inputStruct, err +func UnpackSendWarpMessageInput(input []byte) ([]byte, error) { + res, err := WarpABI.UnpackInput("sendWarpMessage", input) + if err != nil { + return []byte{}, err + } + unpacked := *abi.ConvertType(res[0], new([]byte)).(*[]byte) + return unpacked, nil } -// PackSendWarpMessage packs [inputStruct] of type SendWarpMessageInput into the appropriate arguments for sendWarpMessage. -func PackSendWarpMessage(inputStruct SendWarpMessageInput) ([]byte, error) { - return WarpABI.Pack("sendWarpMessage", inputStruct.DestinationChainID, inputStruct.DestinationAddress, inputStruct.Payload) +// PackSendWarpMessage packs [inputStruct] of type []byte into the appropriate arguments for sendWarpMessage. +func PackSendWarpMessage(payload []byte) ([]byte, error) { + return WarpABI.Pack("sendWarpMessage", payload) } // sendWarpMessage constructs an Avalanche Warp Message containing an AddressedPayload and emits a log to signal validators that they should @@ -224,23 +219,18 @@ func sendWarpMessage(accessibleState contract.AccessibleState, caller common.Add return nil, remainingGas, vmerrs.ErrWriteProtection } // unpack the arguments - inputStruct, err := UnpackSendWarpMessageInput(input) + payload, err := UnpackSendWarpMessageInput(input) if err != nil { return nil, remainingGas, fmt.Errorf("%w: %s", errInvalidSendInput, err) } var ( - sourceChainID = accessibleState.GetSnowContext().ChainID - destinationChainID = inputStruct.DestinationChainID - sourceAddress = caller - destinationAddress = inputStruct.DestinationAddress - payload = inputStruct.Payload + sourceChainID = accessibleState.GetSnowContext().ChainID + sourceAddress = caller ) addressedPayload, err := warpPayload.NewAddressedPayload( sourceAddress, - destinationChainID, - destinationAddress, payload, ) if err != nil { @@ -260,8 +250,6 @@ func sendWarpMessage(accessibleState contract.AccessibleState, caller common.Add ContractAddress, []common.Hash{ WarpABI.Events["SendWarpMessage"].ID, - destinationChainID, - destinationAddress.Hash(), sourceAddress.Hash(), }, unsignedWarpMessage.Bytes(), diff --git a/x/warp/contract_test.go b/x/warp/contract_test.go index e8d763159f..220adacd7d 100644 --- a/x/warp/contract_test.go +++ b/x/warp/contract_test.go @@ -83,18 +83,12 @@ func TestGetBlockchainID(t *testing.T) { func TestSendWarpMessage(t *testing.T) { callerAddr := common.HexToAddress("0x0123") - receiverAddr := common.HexToAddress("0x456789") defaultSnowCtx := snow.DefaultContextTest() blockchainID := defaultSnowCtx.ChainID - destinationChainID := ids.GenerateTestID() sendWarpMessagePayload := utils.RandomBytes(100) - sendWarpMessageInput, err := PackSendWarpMessage(SendWarpMessageInput{ - DestinationChainID: common.Hash(destinationChainID), - DestinationAddress: receiverAddr, - Payload: sendWarpMessagePayload, - }) + sendWarpMessageInput, err := PackSendWarpMessage(sendWarpMessagePayload) require.NoError(t, err) tests := map[string]testutils.PrecompileTest{ @@ -146,8 +140,6 @@ func TestSendWarpMessage(t *testing.T) { require.Equal(t, addressedPayload.SourceAddress, callerAddr) require.Equal(t, unsignedWarpMsg.SourceChainID, blockchainID) - require.Equal(t, addressedPayload.DestinationChainID, common.Hash(destinationChainID)) - require.Equal(t, addressedPayload.DestinationAddress, receiverAddr) require.Equal(t, addressedPayload.Payload, sendWarpMessagePayload) }, }, @@ -160,13 +152,10 @@ func TestGetVerifiedWarpMessage(t *testing.T) { networkID := uint32(54321) callerAddr := common.HexToAddress("0x0123") sourceAddress := common.HexToAddress("0x456789") - destinationAddress := common.HexToAddress("0x987654") sourceChainID := ids.GenerateTestID() packagedPayloadBytes := []byte("mcsorley") addressedPayload, err := warpPayload.NewAddressedPayload( sourceAddress, - common.Hash(destinationChainID), - destinationAddress, packagedPayloadBytes, ) require.NoError(t, err) @@ -195,8 +184,6 @@ func TestGetVerifiedWarpMessage(t *testing.T) { Message: WarpMessage{ SourceChainID: common.Hash(sourceChainID), OriginSenderAddress: sourceAddress, - DestinationChainID: common.Hash(destinationChainID), - DestinationAddress: destinationAddress, Payload: packagedPayloadBytes, }, Valid: true, @@ -254,8 +241,6 @@ func TestGetVerifiedWarpMessage(t *testing.T) { Message: WarpMessage{ SourceChainID: common.Hash(sourceChainID), OriginSenderAddress: sourceAddress, - DestinationChainID: common.Hash(destinationChainID), - DestinationAddress: destinationAddress, Payload: packagedPayloadBytes, }, Valid: true, @@ -298,8 +283,6 @@ func TestGetVerifiedWarpMessage(t *testing.T) { Message: WarpMessage{ SourceChainID: common.Hash(sourceChainID), OriginSenderAddress: sourceAddress, - DestinationChainID: common.Hash(destinationChainID), - DestinationAddress: destinationAddress, Payload: packagedPayloadBytes, }, Valid: true, diff --git a/x/warp/contract_warp_handler.go b/x/warp/contract_warp_handler.go index e2dd907fba..18792a3df0 100644 --- a/x/warp/contract_warp_handler.go +++ b/x/warp/contract_warp_handler.go @@ -108,8 +108,6 @@ func (addressedPayloadHandler) handleMessage(warpMessage *warp.Message) ([]byte, Message: WarpMessage{ SourceChainID: common.Hash(warpMessage.SourceChainID), OriginSenderAddress: addressedPayload.SourceAddress, - DestinationChainID: addressedPayload.DestinationChainID, - DestinationAddress: addressedPayload.DestinationAddress, Payload: addressedPayload.Payload, }, Valid: true, diff --git a/x/warp/predicate_test.go b/x/warp/predicate_test.go index fbb468091f..91cb4ca911 100644 --- a/x/warp/predicate_test.go +++ b/x/warp/predicate_test.go @@ -34,11 +34,10 @@ const pChainHeight uint64 = 1337 var ( _ utils.Sortable[*testValidator] = (*testValidator)(nil) - errTest = errors.New("non-nil error") - networkID = uint32(54321) - sourceChainID = ids.GenerateTestID() - sourceSubnetID = ids.GenerateTestID() - destinationChainID = ids.GenerateTestID() + errTest = errors.New("non-nil error") + networkID = uint32(54321) + sourceChainID = ids.GenerateTestID() + sourceSubnetID = ids.GenerateTestID() // valid unsigned warp message used throughout testing unsignedMsg *avalancheWarp.UnsignedMessage @@ -83,8 +82,6 @@ func init() { var err error addressedPayload, err = warpPayload.NewAddressedPayload( - common.Address(ids.GenerateTestShortID()), - common.Hash(destinationChainID), common.Address(ids.GenerateTestShortID()), []byte{1, 2, 3}, ) From 920e3030a2bd69faefcbeb72f7575cb02a169af2 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Tue, 3 Oct 2023 19:36:59 -0400 Subject: [PATCH 29/52] Rename network signer (#885) * remove unused code * remove signature aggregation job * appease linter * remove signature aggregation job * move all signature aggregeation logic to AggregateSignatures * nit * nit * nit * add mock and tests * clean up tests * nit * don't autogen SignatureGetter because it requires manual changes because gomock is broken * nit * comment * comments * lower log level * re-add todo * move SIgnatureGetter declaration * change file name * nits * use timer instead of time.After (#888) * nit * refactor aggregeateSignatures * comment nits * add tests * remove semantic changes * use error from avalancheWap; combine functions; nits * add logs * typo fix --- plugin/evm/vm.go | 4 +++- warp/aggregator/aggregator.go | 6 ------ ...signature_backend.go => signature_getter.go} | 17 +++++++++++------ 3 files changed, 14 insertions(+), 13 deletions(-) rename warp/aggregator/{network_signature_backend.go => signature_getter.go} (77%) diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index a6ebed2339..bd48b6638d 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -943,7 +943,9 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]*commonEng.HTTPHandler } if vm.config.WarpAPIEnabled { - warpAggregator := aggregator.New(vm.ctx.SubnetID, warpValidators.NewState(vm.ctx), &aggregator.NetworkSigner{Client: vm.client}) + validatorsState := warpValidators.NewState(vm.ctx) + signatureGetter := &aggregator.NetworkSignatureGetter{Client: vm.client} + warpAggregator := aggregator.New(vm.ctx.SubnetID, validatorsState, signatureGetter) if err := handler.RegisterName("warp", warp.NewAPI(vm.warpBackend, warpAggregator)); err != nil { return nil, err } diff --git a/warp/aggregator/aggregator.go b/warp/aggregator/aggregator.go index 8633205373..d5c98fb50f 100644 --- a/warp/aggregator/aggregator.go +++ b/warp/aggregator/aggregator.go @@ -21,12 +21,6 @@ import ( var errNoValidators = errors.New("cannot aggregate signatures from subnet with no validators") -// SignatureGetter defines the minimum network interface to perform signature aggregation -type SignatureGetter interface { - // GetSignature attempts to fetch a BLS Signature from [nodeID] for [unsignedWarpMessage] - GetSignature(ctx context.Context, nodeID ids.NodeID, unsignedWarpMessage *avalancheWarp.UnsignedMessage) (*bls.Signature, error) -} - type AggregateSignatureResult struct { // Weight of validators included in the aggregate signature. SignatureWeight uint64 diff --git a/warp/aggregator/network_signature_backend.go b/warp/aggregator/signature_getter.go similarity index 77% rename from warp/aggregator/network_signature_backend.go rename to warp/aggregator/signature_getter.go index 4ed89e826d..154d5da04a 100644 --- a/warp/aggregator/network_signature_backend.go +++ b/warp/aggregator/signature_getter.go @@ -20,14 +20,21 @@ const ( retryBackoffFactor = 2 ) -var _ SignatureGetter = (*NetworkSigner)(nil) +var _ SignatureGetter = (*NetworkSignatureGetter)(nil) + +// SignatureGetter defines the minimum network interface to perform signature aggregation +type SignatureGetter interface { + // GetSignature attempts to fetch a BLS Signature from [nodeID] for [unsignedWarpMessage] + GetSignature(ctx context.Context, nodeID ids.NodeID, unsignedWarpMessage *avalancheWarp.UnsignedMessage) (*bls.Signature, error) +} type NetworkClient interface { SendAppRequest(nodeID ids.NodeID, message []byte) ([]byte, error) } -// NetworkSigner fetches warp signatures on behalf of the aggregator using VM App-Specific Messaging -type NetworkSigner struct { +// NetworkSignatureGetter fetches warp signatures on behalf of the +// aggregator using VM App-Specific Messaging +type NetworkSignatureGetter struct { Client NetworkClient } @@ -35,7 +42,7 @@ type NetworkSigner struct { // // Note: this function will continue attempting to fetch the signature from [nodeID] until it receives an invalid value or [ctx] is cancelled. // The caller is responsible to cancel [ctx] if it no longer needs to fetch this signature. -func (s *NetworkSigner) GetSignature(ctx context.Context, nodeID ids.NodeID, unsignedWarpMessage *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { +func (s *NetworkSignatureGetter) GetSignature(ctx context.Context, nodeID ids.NodeID, unsignedWarpMessage *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { signatureReq := message.SignatureRequest{ MessageID: unsignedWarpMessage.ID(), } @@ -71,12 +78,10 @@ func (s *NetworkSigner) GetSignature(ctx context.Context, nodeID ids.NodeID, uns } continue } - var response message.SignatureResponse if _, err := message.Codec.Unmarshal(signatureRes, &response); err != nil { return nil, fmt.Errorf("failed to unmarshal signature res: %w", err) } - blsSignature, err := bls.SignatureFromBytes(response.Signature[:]) if err != nil { return nil, fmt.Errorf("failed to parse signature from res: %w", err) From 9b6d4bac6fd6d36e4936750a280f6cada05e8e15 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Tue, 3 Oct 2023 19:51:51 -0400 Subject: [PATCH 30/52] Add contexts (#874) * add context to SendAppRequest signature * stop waiting on app response upon context cancellation * add contexts in other network send functions * crosschain cancellation * app request cancellation * app request any on cancellation * fixes * change ErrorContains to ErrorIs * treat as fatal TODO * use context * simplify texts * test nits * comment; make responseChan buffer length 1 * update test * peer: remove TODOs for treating appSender err as fatal --- peer/client.go | 57 +++++---- peer/network.go | 28 ++--- peer/network_test.go | 181 ++++++++++++++++++++++++---- peer/waiting_handler.go | 7 +- sync/client/client.go | 4 +- sync/client/mock_network.go | 7 +- warp/aggregator/signature_getter.go | 4 +- 7 files changed, 224 insertions(+), 64 deletions(-) diff --git a/peer/client.go b/peer/client.go index 6a002d0e38..0af5814eb0 100644 --- a/peer/client.go +++ b/peer/client.go @@ -4,6 +4,7 @@ package peer import ( + "context" "errors" "github.com/ava-labs/avalanchego/ids" @@ -23,15 +24,15 @@ type NetworkClient interface { // node version greater than or equal to minVersion. // Returns response bytes, the ID of the chosen peer, and ErrRequestFailed if // the request should be retried. - SendAppRequestAny(minVersion *version.Application, request []byte) ([]byte, ids.NodeID, error) + SendAppRequestAny(ctx context.Context, minVersion *version.Application, request []byte) ([]byte, ids.NodeID, error) // SendAppRequest synchronously sends request to the selected nodeID // Returns response bytes, and ErrRequestFailed if the request should be retried. - SendAppRequest(nodeID ids.NodeID, request []byte) ([]byte, error) + SendAppRequest(ctx context.Context, nodeID ids.NodeID, request []byte) ([]byte, error) // SendCrossChainRequest sends a request to a specific blockchain running on this node. // Returns response bytes, and ErrRequestFailed if the request failed. - SendCrossChainRequest(chainID ids.ID, request []byte) ([]byte, error) + SendCrossChainRequest(ctx context.Context, chainID ids.ID, request []byte) ([]byte, error) // Gossip sends given gossip message to peers Gossip(gossip []byte) error @@ -59,45 +60,59 @@ func NewNetworkClient(network Network) NetworkClient { // node version greater than or equal to minVersion. // Returns response bytes, the ID of the chosen peer, and ErrRequestFailed if // the request should be retried. -func (c *client) SendAppRequestAny(minVersion *version.Application, request []byte) ([]byte, ids.NodeID, error) { +func (c *client) SendAppRequestAny(ctx context.Context, minVersion *version.Application, request []byte) ([]byte, ids.NodeID, error) { waitingHandler := newWaitingResponseHandler() - nodeID, err := c.network.SendAppRequestAny(minVersion, request, waitingHandler) + nodeID, err := c.network.SendAppRequestAny(ctx, minVersion, request, waitingHandler) if err != nil { return nil, nodeID, err } - response := <-waitingHandler.responseChan - if waitingHandler.failed { - return nil, nodeID, ErrRequestFailed + + select { + case <-ctx.Done(): + return nil, nodeID, ctx.Err() + case response := <-waitingHandler.responseChan: + if waitingHandler.failed { + return nil, nodeID, ErrRequestFailed + } + return response, nodeID, nil } - return response, nodeID, nil } // SendAppRequest synchronously sends request to the specified nodeID // Returns response bytes and ErrRequestFailed if the request should be retried. -func (c *client) SendAppRequest(nodeID ids.NodeID, request []byte) ([]byte, error) { +func (c *client) SendAppRequest(ctx context.Context, nodeID ids.NodeID, request []byte) ([]byte, error) { waitingHandler := newWaitingResponseHandler() - if err := c.network.SendAppRequest(nodeID, request, waitingHandler); err != nil { + if err := c.network.SendAppRequest(ctx, nodeID, request, waitingHandler); err != nil { return nil, err } - response := <-waitingHandler.responseChan - if waitingHandler.failed { - return nil, ErrRequestFailed + + select { + case <-ctx.Done(): + return nil, ctx.Err() + case response := <-waitingHandler.responseChan: + if waitingHandler.failed { + return nil, ErrRequestFailed + } + return response, nil } - return response, nil } // SendCrossChainRequest synchronously sends request to the specified chainID // Returns response bytes and ErrRequestFailed if the request should be retried. -func (c *client) SendCrossChainRequest(chainID ids.ID, request []byte) ([]byte, error) { +func (c *client) SendCrossChainRequest(ctx context.Context, chainID ids.ID, request []byte) ([]byte, error) { waitingHandler := newWaitingResponseHandler() - if err := c.network.SendCrossChainRequest(chainID, request, waitingHandler); err != nil { + if err := c.network.SendCrossChainRequest(ctx, chainID, request, waitingHandler); err != nil { return nil, err } - response := <-waitingHandler.responseChan - if waitingHandler.failed { - return nil, ErrRequestFailed + select { + case <-ctx.Done(): + return nil, ctx.Err() + case response := <-waitingHandler.responseChan: + if waitingHandler.failed { + return nil, ErrRequestFailed + } + return response, nil } - return response, nil } func (c *client) Gossip(gossip []byte) error { diff --git a/peer/network.go b/peer/network.go index d6176ea391..63f06b40e0 100644 --- a/peer/network.go +++ b/peer/network.go @@ -46,16 +46,16 @@ type Network interface { // node version greater than or equal to minVersion. // Returns the ID of the chosen peer, and an error if the request could not // be sent to a peer with the desired [minVersion]. - SendAppRequestAny(minVersion *version.Application, message []byte, handler message.ResponseHandler) (ids.NodeID, error) + SendAppRequestAny(ctx context.Context, minVersion *version.Application, message []byte, handler message.ResponseHandler) (ids.NodeID, error) // SendAppRequest sends message to given nodeID, notifying handler when there's a response or timeout - SendAppRequest(nodeID ids.NodeID, message []byte, handler message.ResponseHandler) error + SendAppRequest(ctx context.Context, nodeID ids.NodeID, message []byte, handler message.ResponseHandler) error // Gossip sends given gossip message to peers Gossip(gossip []byte) error // SendCrossChainRequest sends a message to given chainID notifying handler when there's a response or timeout - SendCrossChainRequest(chainID ids.ID, message []byte, handler message.ResponseHandler) error + SendCrossChainRequest(ctx context.Context, chainID ids.ID, message []byte, handler message.ResponseHandler) error // Shutdown stops all peer channel listeners and marks the node to have stopped // n.Start() can be called again but the peers will have to be reconnected @@ -134,16 +134,16 @@ func NewNetwork(router *p2p.Router, appSender common.AppSender, codec codec.Mana // the request will be sent to any peer regardless of their version. // Returns the ID of the chosen peer, and an error if the request could not // be sent to a peer with the desired [minVersion]. -func (n *network) SendAppRequestAny(minVersion *version.Application, request []byte, handler message.ResponseHandler) (ids.NodeID, error) { +func (n *network) SendAppRequestAny(ctx context.Context, minVersion *version.Application, request []byte, handler message.ResponseHandler) (ids.NodeID, error) { // Take a slot from total [activeAppRequests] and block until a slot becomes available. - if err := n.activeAppRequests.Acquire(context.Background(), 1); err != nil { + if err := n.activeAppRequests.Acquire(ctx, 1); err != nil { return ids.EmptyNodeID, errAcquiringSemaphore } n.lock.Lock() defer n.lock.Unlock() if nodeID, ok := n.peers.GetAnyPeer(minVersion); ok { - return nodeID, n.sendAppRequest(nodeID, request, handler) + return nodeID, n.sendAppRequest(ctx, nodeID, request, handler) } n.activeAppRequests.Release(1) @@ -151,20 +151,20 @@ func (n *network) SendAppRequestAny(minVersion *version.Application, request []b } // SendAppRequest sends request message bytes to specified nodeID, notifying the responseHandler on response or failure -func (n *network) SendAppRequest(nodeID ids.NodeID, request []byte, responseHandler message.ResponseHandler) error { +func (n *network) SendAppRequest(ctx context.Context, nodeID ids.NodeID, request []byte, responseHandler message.ResponseHandler) error { if nodeID == ids.EmptyNodeID { return fmt.Errorf("cannot send request to empty nodeID, nodeID=%s, requestLen=%d", nodeID, len(request)) } // Take a slot from total [activeAppRequests] and block until a slot becomes available. - if err := n.activeAppRequests.Acquire(context.Background(), 1); err != nil { + if err := n.activeAppRequests.Acquire(ctx, 1); err != nil { return errAcquiringSemaphore } n.lock.Lock() defer n.lock.Unlock() - return n.sendAppRequest(nodeID, request, responseHandler) + return n.sendAppRequest(ctx, nodeID, request, responseHandler) } // sendAppRequest sends request message bytes to specified nodeID and adds [responseHandler] to [outstandingRequestHandlers] @@ -173,7 +173,7 @@ func (n *network) SendAppRequest(nodeID ids.NodeID, request []byte, responseHand // Releases active requests semaphore if there was an error in sending the request // Returns an error if [appSender] is unable to make the request. // Assumes write lock is held -func (n *network) sendAppRequest(nodeID ids.NodeID, request []byte, responseHandler message.ResponseHandler) error { +func (n *network) sendAppRequest(ctx context.Context, nodeID ids.NodeID, request []byte, responseHandler message.ResponseHandler) error { if n.closed.Get() { n.activeAppRequests.Release(1) return nil @@ -190,7 +190,7 @@ func (n *network) sendAppRequest(nodeID ids.NodeID, request []byte, responseHand // Send app request to [nodeID]. // On failure, release the slot from [activeAppRequests] and delete request from [outstandingRequestHandlers] - if err := n.appSender.SendAppRequest(context.TODO(), nodeIDs, requestID, request); err != nil { + if err := n.appSender.SendAppRequest(ctx, nodeIDs, requestID, request); err != nil { n.activeAppRequests.Release(1) delete(n.outstandingRequestHandlers, requestID) return err @@ -203,9 +203,9 @@ func (n *network) sendAppRequest(nodeID ids.NodeID, request []byte, responseHand // SendCrossChainRequest sends request message bytes to specified chainID and adds [handler] to [outstandingRequestHandlers] // so that it can be invoked when the network receives either a response or failure message. // Returns an error if [appSender] is unable to make the request. -func (n *network) SendCrossChainRequest(chainID ids.ID, request []byte, handler message.ResponseHandler) error { +func (n *network) SendCrossChainRequest(ctx context.Context, chainID ids.ID, request []byte, handler message.ResponseHandler) error { // Take a slot from total [activeCrossChainRequests] and block until a slot becomes available. - if err := n.activeCrossChainRequests.Acquire(context.Background(), 1); err != nil { + if err := n.activeCrossChainRequests.Acquire(ctx, 1); err != nil { return errAcquiringSemaphore } @@ -222,7 +222,7 @@ func (n *network) SendCrossChainRequest(chainID ids.ID, request []byte, handler // Send cross chain request to [chainID]. // On failure, release the slot from [activeCrossChainRequests] and delete request from [outstandingRequestHandlers]. - if err := n.appSender.SendCrossChainAppRequest(context.TODO(), chainID, requestID, request); err != nil { + if err := n.appSender.SendCrossChainAppRequest(ctx, chainID, requestID, request); err != nil { n.activeCrossChainRequests.Release(1) delete(n.outstandingRequestHandlers, requestID) return err diff --git a/peer/network_test.go b/peer/network_test.go index 3ad5e11831..6ddb66cfee 100644 --- a/peer/network_test.go +++ b/peer/network_test.go @@ -67,7 +67,7 @@ func TestRequestAnyRequestsRoutingAndResponse(t *testing.T) { senderWg := &sync.WaitGroup{} var net Network sender := testAppSender{ - sendAppRequestFn: func(nodes set.Set[ids.NodeID], requestID uint32, requestBytes []byte) error { + sendAppRequestFn: func(_ context.Context, nodes set.Set[ids.NodeID], requestID uint32, requestBytes []byte) error { nodeID, _ := nodes.Pop() senderWg.Add(1) go func() { @@ -115,7 +115,7 @@ func TestRequestAnyRequestsRoutingAndResponse(t *testing.T) { defer wg.Done() requestBytes, err := message.RequestToBytes(codecManager, requestMessage) assert.NoError(t, err) - responseBytes, _, err := client.SendAppRequestAny(defaultPeerVersion, requestBytes) + responseBytes, _, err := client.SendAppRequestAny(context.Background(), defaultPeerVersion, requestBytes) assert.NoError(t, err) assert.NotNil(t, responseBytes) @@ -132,6 +132,35 @@ func TestRequestAnyRequestsRoutingAndResponse(t *testing.T) { assert.Equal(t, totalCalls, int(atomic.LoadUint32(&callNum))) } +func TestAppRequestOnCtxCancellation(t *testing.T) { + codecManager := buildCodec(t, HelloRequest{}, HelloResponse{}) + crossChainCodecManager := buildCodec(t, ExampleCrossChainRequest{}, ExampleCrossChainResponse{}) + + sender := testAppSender{ + sendAppRequestFn: func(_ context.Context, nodes set.Set[ids.NodeID], requestID uint32, requestBytes []byte) error { + return nil + }, + sendAppResponseFn: func(nodeID ids.NodeID, requestID uint32, responseBytes []byte) error { + return nil + }, + } + + net := NewNetwork(p2p.NewRouter(logging.NoLog{}, nil, prometheus.NewRegistry(), ""), sender, codecManager, crossChainCodecManager, ids.EmptyNodeID, 1, 1) + net.SetRequestHandler(&HelloGreetingRequestHandler{codec: codecManager}) + + requestMessage := HelloRequest{Message: "this is a request"} + requestBytes, err := message.RequestToBytes(codecManager, requestMessage) + assert.NoError(t, err) + + nodeID := ids.GenerateTestNodeID() + ctx, cancel := context.WithCancel(context.Background()) + // cancel context prior to sending + cancel() + client := NewNetworkClient(net) + _, err = client.SendAppRequest(ctx, nodeID, requestBytes) + assert.ErrorIs(t, err, context.Canceled) +} + func TestRequestRequestsRoutingAndResponse(t *testing.T) { callNum := uint32(0) senderWg := &sync.WaitGroup{} @@ -139,7 +168,7 @@ func TestRequestRequestsRoutingAndResponse(t *testing.T) { var lock sync.Mutex contactedNodes := make(map[ids.NodeID]struct{}) sender := testAppSender{ - sendAppRequestFn: func(nodes set.Set[ids.NodeID], requestID uint32, requestBytes []byte) error { + sendAppRequestFn: func(_ context.Context, nodes set.Set[ids.NodeID], requestID uint32, requestBytes []byte) error { nodeID, _ := nodes.Pop() lock.Lock() contactedNodes[nodeID] = struct{}{} @@ -200,7 +229,7 @@ func TestRequestRequestsRoutingAndResponse(t *testing.T) { defer wg.Done() requestBytes, err := message.RequestToBytes(codecManager, requestMessage) assert.NoError(t, err) - responseBytes, err := client.SendAppRequest(nodeID, requestBytes) + responseBytes, err := client.SendAppRequest(context.Background(), nodeID, requestBytes) assert.NoError(t, err) assert.NotNil(t, responseBytes) @@ -222,7 +251,7 @@ func TestRequestRequestsRoutingAndResponse(t *testing.T) { } // ensure empty nodeID is not allowed - _, err := client.SendAppRequest(ids.EmptyNodeID, []byte("hello there")) + _, err := client.SendAppRequest(context.Background(), ids.EmptyNodeID, []byte("hello there")) assert.Error(t, err) assert.Contains(t, err.Error(), "cannot send request to empty nodeID") } @@ -234,7 +263,7 @@ func TestAppRequestOnShutdown(t *testing.T) { called bool ) sender := testAppSender{ - sendAppRequestFn: func(nodes set.Set[ids.NodeID], requestID uint32, requestBytes []byte) error { + sendAppRequestFn: func(_ context.Context, nodes set.Set[ids.NodeID], requestID uint32, requestBytes []byte) error { wg.Add(1) go func() { called = true @@ -261,7 +290,7 @@ func TestAppRequestOnShutdown(t *testing.T) { defer wg.Done() requestBytes, err := message.RequestToBytes(codecManager, requestMessage) require.NoError(t, err) - responseBytes, _, err := client.SendAppRequestAny(defaultPeerVersion, requestBytes) + responseBytes, _, err := client.SendAppRequestAny(context.Background(), defaultPeerVersion, requestBytes) require.Error(t, err, ErrRequestFailed) require.Nil(t, responseBytes) }() @@ -269,6 +298,83 @@ func TestAppRequestOnShutdown(t *testing.T) { require.True(t, called) } +func TestAppRequestAnyOnCtxCancellation(t *testing.T) { + codecManager := buildCodec(t, HelloRequest{}, HelloResponse{}) + crossChainCodecManager := buildCodec(t, ExampleCrossChainRequest{}, ExampleCrossChainResponse{}) + + type reqInfo struct { + nodeID ids.NodeID + requestID uint32 + } + sentAppRequest := make(chan reqInfo, 1) + + sender := testAppSender{ + sendAppRequestFn: func(ctx context.Context, nodes set.Set[ids.NodeID], requestID uint32, requestBytes []byte) error { + if err := ctx.Err(); err != nil { + return err + } + + assert.Len(t, nodes, 1) + sentAppRequest <- reqInfo{ + nodeID: nodes.List()[0], + requestID: requestID, + } + return nil + }, + sendAppResponseFn: func(nodeID ids.NodeID, requestID uint32, responseBytes []byte) error { + return nil + }, + } + + net := NewNetwork(p2p.NewRouter(logging.NoLog{}, nil, prometheus.NewRegistry(), ""), sender, codecManager, crossChainCodecManager, ids.EmptyNodeID, 1, 1) + net.SetRequestHandler(&HelloGreetingRequestHandler{codec: codecManager}) + assert.NoError(t, + net.Connected( + context.Background(), + ids.GenerateTestNodeID(), + version.CurrentApp, + ), + ) + + requestMessage := HelloRequest{Message: "this is a request"} + requestBytes, err := message.RequestToBytes(codecManager, requestMessage) + assert.NoError(t, err) + + // cancel context prior to sending + ctx, cancel := context.WithCancel(context.Background()) + cancel() + client := NewNetworkClient(net) + _, _, err = client.SendAppRequestAny(ctx, defaultPeerVersion, requestBytes) + assert.ErrorIs(t, err, context.Canceled) + // Assert we didn't send anything + select { + case <-sentAppRequest: + assert.FailNow(t, "should not have sent request") + default: + } + + // Cancel context after sending + assert.Empty(t, net.(*network).outstandingRequestHandlers) // no outstanding requests + ctx, cancel = context.WithCancel(context.Background()) + doneChan := make(chan struct{}) + go func() { + _, _, err = client.SendAppRequestAny(ctx, defaultPeerVersion, requestBytes) + assert.ErrorIs(t, err, context.Canceled) + close(doneChan) + }() + // Wait until we've "sent" the app request over the network + // before cancelling context. + sentAppRequestInfo := <-sentAppRequest + assert.Len(t, net.(*network).outstandingRequestHandlers, 1) + cancel() + <-doneChan + // Should still be able to process a response after cancelling. + assert.Len(t, net.(*network).outstandingRequestHandlers, 1) // context cancellation SendAppRequestAny failure doesn't clear + err = net.AppResponse(context.Background(), sentAppRequestInfo.nodeID, sentAppRequestInfo.requestID, []byte{}) + assert.NoError(t, err) + assert.Empty(t, net.(*network).outstandingRequestHandlers) // Received response +} + func TestRequestMinVersion(t *testing.T) { callNum := uint32(0) nodeID := ids.GenerateTestNodeID() @@ -276,7 +382,7 @@ func TestRequestMinVersion(t *testing.T) { var net Network sender := testAppSender{ - sendAppRequestFn: func(nodes set.Set[ids.NodeID], reqID uint32, messageBytes []byte) error { + sendAppRequestFn: func(_ context.Context, nodes set.Set[ids.NodeID], reqID uint32, messageBytes []byte) error { atomic.AddUint32(&callNum, 1) assert.True(t, nodes.Contains(nodeID), "request nodes should contain expected nodeID") assert.Len(t, nodes, 1, "request nodes should contain exactly one node") @@ -316,6 +422,7 @@ func TestRequestMinVersion(t *testing.T) { // ensure version does not match responseBytes, _, err := client.SendAppRequestAny( + context.Background(), &version.Application{ Major: 2, Minor: 0, @@ -327,7 +434,7 @@ func TestRequestMinVersion(t *testing.T) { assert.Nil(t, responseBytes) // ensure version matches and the request goes through - responseBytes, _, err = client.SendAppRequestAny(defaultPeerVersion, requestBytes) + responseBytes, _, err = client.SendAppRequestAny(context.Background(), defaultPeerVersion, requestBytes) assert.NoError(t, err) var response TestMessage @@ -341,7 +448,7 @@ func TestOnRequestHonoursDeadline(t *testing.T) { var net Network responded := false sender := testAppSender{ - sendAppRequestFn: func(nodes set.Set[ids.NodeID], reqID uint32, message []byte) error { + sendAppRequestFn: func(_ context.Context, nodes set.Set[ids.NodeID], reqID uint32, message []byte) error { return nil }, sendAppResponseFn: func(nodeID ids.NodeID, reqID uint32, message []byte) error { @@ -528,7 +635,7 @@ func TestCrossChainAppRequest(t *testing.T) { assert.NoError(t, err) chainID := ids.ID(ethcommon.BytesToHash([]byte{1, 2, 3, 4, 5})) - responseBytes, err := client.SendCrossChainRequest(chainID, crossChainRequest) + responseBytes, err := client.SendCrossChainRequest(context.Background(), chainID, crossChainRequest) assert.NoError(t, err) var response ExampleCrossChainResponse @@ -538,6 +645,38 @@ func TestCrossChainAppRequest(t *testing.T) { assert.Equal(t, "this is an example response", response.Response) } +func TestCrossChainAppRequestOnCtxCancellation(t *testing.T) { + codecManager := buildCodec(t, TestMessage{}) + crossChainCodecManager := buildCodec(t, ExampleCrossChainRequest{}, ExampleCrossChainResponse{}) + + sender := testAppSender{ + sendCrossChainAppRequestFn: func(requestingChainID ids.ID, requestID uint32, requestBytes []byte) error { + return nil + }, + sendCrossChainAppResponseFn: func(respondingChainID ids.ID, requestID uint32, responseBytes []byte) error { + return nil + }, + } + + net := NewNetwork(p2p.NewRouter(logging.NoLog{}, nil, prometheus.NewRegistry(), ""), sender, codecManager, crossChainCodecManager, ids.EmptyNodeID, 1, 1) + net.SetCrossChainRequestHandler(&testCrossChainHandler{codec: crossChainCodecManager}) + + exampleCrossChainRequest := ExampleCrossChainRequest{ + Message: "hello this is an example request", + } + + crossChainRequest, err := buildCrossChainRequest(crossChainCodecManager, exampleCrossChainRequest) + assert.NoError(t, err) + + chainID := ids.ID(ethcommon.BytesToHash([]byte{1, 2, 3, 4, 5})) + ctx, cancel := context.WithCancel(context.Background()) + // cancel context prior to sending + cancel() + client := NewNetworkClient(net) + _, err = client.SendCrossChainRequest(ctx, chainID, crossChainRequest) + assert.ErrorIs(t, err, context.Canceled) +} + func TestCrossChainRequestRequestsRoutingAndResponse(t *testing.T) { var ( callNum uint32 @@ -594,7 +733,7 @@ func TestCrossChainRequestRequestsRoutingAndResponse(t *testing.T) { defer requestWg.Done() crossChainRequest, err := buildCrossChainRequest(crossChainCodecManager, exampleCrossChainRequest) assert.NoError(t, err) - responseBytes, err := client.SendCrossChainRequest(chainID, crossChainRequest) + responseBytes, err := client.SendCrossChainRequest(context.Background(), chainID, crossChainRequest) assert.NoError(t, err) assert.NotNil(t, responseBytes) @@ -644,7 +783,7 @@ func TestCrossChainRequestOnShutdown(t *testing.T) { defer wg.Done() crossChainRequest, err := buildCrossChainRequest(crossChainCodecManager, exampleCrossChainRequest) require.NoError(t, err) - responseBytes, err := client.SendCrossChainRequest(chainID, crossChainRequest) + responseBytes, err := client.SendCrossChainRequest(context.Background(), chainID, crossChainRequest) require.ErrorIs(t, err, ErrRequestFailed) require.Nil(t, responseBytes) }() @@ -658,8 +797,8 @@ func TestNetworkAppRequestAfterShutdown(t *testing.T) { net := NewNetwork(nil, nil, nil, nil, ids.EmptyNodeID, 1, 0) net.Shutdown() - require.NoError(net.SendAppRequest(ids.GenerateTestNodeID(), nil, nil)) - require.NoError(net.SendAppRequest(ids.GenerateTestNodeID(), nil, nil)) + require.NoError(net.SendAppRequest(context.Background(), ids.GenerateTestNodeID(), nil, nil)) + require.NoError(net.SendAppRequest(context.Background(), ids.GenerateTestNodeID(), nil, nil)) } func TestNetworkCrossChainAppRequestAfterShutdown(t *testing.T) { @@ -668,14 +807,14 @@ func TestNetworkCrossChainAppRequestAfterShutdown(t *testing.T) { net := NewNetwork(nil, nil, nil, nil, ids.EmptyNodeID, 0, 1) net.Shutdown() - require.NoError(net.SendCrossChainRequest(ids.GenerateTestID(), nil, nil)) - require.NoError(net.SendCrossChainRequest(ids.GenerateTestID(), nil, nil)) + require.NoError(net.SendCrossChainRequest(context.Background(), ids.GenerateTestID(), nil, nil)) + require.NoError(net.SendCrossChainRequest(context.Background(), ids.GenerateTestID(), nil, nil)) } func TestSDKRouting(t *testing.T) { require := require.New(t) sender := &testAppSender{ - sendAppRequestFn: func(s set.Set[ids.NodeID], u uint32, bytes []byte) error { + sendAppRequestFn: func(_ context.Context, s set.Set[ids.NodeID], u uint32, bytes []byte) error { return nil }, sendAppResponseFn: func(id ids.NodeID, u uint32, bytes []byte) error { @@ -741,7 +880,7 @@ func buildCrossChainRequest(codec codec.Manager, msg message.CrossChainRequest) type testAppSender struct { sendCrossChainAppRequestFn func(ids.ID, uint32, []byte) error sendCrossChainAppResponseFn func(ids.ID, uint32, []byte) error - sendAppRequestFn func(set.Set[ids.NodeID], uint32, []byte) error + sendAppRequestFn func(context.Context, set.Set[ids.NodeID], uint32, []byte) error sendAppResponseFn func(ids.NodeID, uint32, []byte) error sendAppGossipFn func([]byte) error } @@ -758,8 +897,8 @@ func (t testAppSender) SendAppGossipSpecific(context.Context, set.Set[ids.NodeID panic("not implemented") } -func (t testAppSender) SendAppRequest(_ context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, message []byte) error { - return t.sendAppRequestFn(nodeIDs, requestID, message) +func (t testAppSender) SendAppRequest(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, message []byte) error { + return t.sendAppRequestFn(ctx, nodeIDs, requestID, message) } func (t testAppSender) SendAppResponse(_ context.Context, nodeID ids.NodeID, requestID uint32, message []byte) error { diff --git a/peer/waiting_handler.go b/peer/waiting_handler.go index 846166c121..e6a7d9fd87 100644 --- a/peer/waiting_handler.go +++ b/peer/waiting_handler.go @@ -34,5 +34,10 @@ func (w *waitingResponseHandler) OnFailure() error { // newWaitingResponseHandler returns new instance of the waitingResponseHandler func newWaitingResponseHandler() *waitingResponseHandler { - return &waitingResponseHandler{responseChan: make(chan []byte)} + return &waitingResponseHandler{ + // Make buffer length 1 so that OnResponse can complete + // even if no goroutine is waiting on the channel (i.e. + // the context of a request is cancelled.) + responseChan: make(chan []byte, 1), + } } diff --git a/sync/client/client.go b/sync/client/client.go index 773baf54c5..5168aa8dda 100644 --- a/sync/client/client.go +++ b/sync/client/client.go @@ -325,14 +325,14 @@ func (c *client) get(ctx context.Context, request message.Request, parseFn parse start time.Time = time.Now() ) if len(c.stateSyncNodes) == 0 { - response, nodeID, err = c.networkClient.SendAppRequestAny(StateSyncVersion, requestBytes) + response, nodeID, err = c.networkClient.SendAppRequestAny(ctx, StateSyncVersion, requestBytes) } else { // get the next nodeID using the nodeIdx offset. If we're out of nodes, loop back to 0 // we do this every attempt to ensure we get a different node each time if possible. nodeIdx := atomic.AddUint32(&c.stateSyncNodeIdx, 1) nodeID = c.stateSyncNodes[nodeIdx%uint32(len(c.stateSyncNodes))] - response, err = c.networkClient.SendAppRequest(nodeID, requestBytes) + response, err = c.networkClient.SendAppRequest(ctx, nodeID, requestBytes) } metric.UpdateRequestLatency(time.Since(start)) diff --git a/sync/client/mock_network.go b/sync/client/mock_network.go index b9729350fa..8e17e3eefa 100644 --- a/sync/client/mock_network.go +++ b/sync/client/mock_network.go @@ -4,6 +4,7 @@ package statesyncclient import ( + "context" "errors" "github.com/ava-labs/avalanchego/ids" @@ -28,7 +29,7 @@ type mockNetwork struct { nodesRequested []ids.NodeID } -func (t *mockNetwork) SendAppRequestAny(minVersion *version.Application, request []byte) ([]byte, ids.NodeID, error) { +func (t *mockNetwork) SendAppRequestAny(ctx context.Context, minVersion *version.Application, request []byte) ([]byte, ids.NodeID, error) { if len(t.response) == 0 { return nil, ids.EmptyNodeID, errors.New("no mocked response to return in mockNetwork") } @@ -39,7 +40,7 @@ func (t *mockNetwork) SendAppRequestAny(minVersion *version.Application, request return response, ids.EmptyNodeID, err } -func (t *mockNetwork) SendAppRequest(nodeID ids.NodeID, request []byte) ([]byte, error) { +func (t *mockNetwork) SendAppRequest(ctx context.Context, nodeID ids.NodeID, request []byte) ([]byte, error) { if len(t.response) == 0 { return nil, errors.New("no mocked response to return in mockNetwork") } @@ -77,7 +78,7 @@ func (t *mockNetwork) Gossip([]byte) error { panic("not implemented") // we don't care about this function for this test } -func (t *mockNetwork) SendCrossChainRequest(chainID ids.ID, request []byte) ([]byte, error) { +func (t *mockNetwork) SendCrossChainRequest(ctx context.Context, chainID ids.ID, request []byte) ([]byte, error) { panic("not implemented") // we don't care about this function for this test } diff --git a/warp/aggregator/signature_getter.go b/warp/aggregator/signature_getter.go index 154d5da04a..526280bc9b 100644 --- a/warp/aggregator/signature_getter.go +++ b/warp/aggregator/signature_getter.go @@ -29,7 +29,7 @@ type SignatureGetter interface { } type NetworkClient interface { - SendAppRequest(nodeID ids.NodeID, message []byte) ([]byte, error) + SendAppRequest(ctx context.Context, nodeID ids.NodeID, message []byte) ([]byte, error) } // NetworkSignatureGetter fetches warp signatures on behalf of the @@ -55,7 +55,7 @@ func (s *NetworkSignatureGetter) GetSignature(ctx context.Context, nodeID ids.No timer := time.NewTimer(delay) defer timer.Stop() for { - signatureRes, err := s.Client.SendAppRequest(nodeID, signatureReqBytes) + signatureRes, err := s.Client.SendAppRequest(ctx, nodeID, signatureReqBytes) // If the client fails to retrieve a response perform an exponential backoff. // Note: it is up to the caller to ensure that [ctx] is eventually cancelled if err != nil { From 738c8a4af7eb83e77617fc8c6989d44ff4de9568 Mon Sep 17 00:00:00 2001 From: tactical_retreat Date: Wed, 4 Oct 2023 10:43:19 -0400 Subject: [PATCH 31/52] t8ntool: tiny bugfix for difficulty field (#933) --- cmd/evm/internal/t8ntool/block.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/evm/internal/t8ntool/block.go b/cmd/evm/internal/t8ntool/block.go index 81adc0f8e4..a5ec44f170 100644 --- a/cmd/evm/internal/t8ntool/block.go +++ b/cmd/evm/internal/t8ntool/block.go @@ -156,7 +156,7 @@ func (i *bbInput) ToBlock() *types.Block { if i.Header.Nonce != nil { header.Nonce = *i.Header.Nonce } - if header.Difficulty != nil { + if i.Header.Difficulty != nil { header.Difficulty = i.Header.Difficulty } return types.NewBlockWithHeader(header).WithBody(i.Txs, i.Ommers) From 49135a84f1f796f56af123164e71367bf85529e0 Mon Sep 17 00:00:00 2001 From: Anusha <63559942+anusha-ctrl@users.noreply.github.com> Date: Wed, 4 Oct 2023 15:02:09 -0700 Subject: [PATCH 32/52] Add Unit Test for Test Aggregate Signatures (#936) * Add Unit Test for Test Aggregate Signatures * cleanup * address comments * nit * remove unnecessary --- warp/aggregator/aggregator_test.go | 179 +++++++++++++++++++++-------- 1 file changed, 128 insertions(+), 51 deletions(-) diff --git a/warp/aggregator/aggregator_test.go b/warp/aggregator/aggregator_test.go index 01225f89c1..5135aa648b 100644 --- a/warp/aggregator/aggregator_test.go +++ b/warp/aggregator/aggregator_test.go @@ -76,20 +76,22 @@ func TestAggregateSignatures(t *testing.T) { } type test struct { - name string - contextFunc func() context.Context - aggregatorFunc func(*gomock.Controller) *Aggregator - unsignedMsg *avalancheWarp.UnsignedMessage - quorumNum uint64 - expectedSigners []*avalancheWarp.Validator - expectedErr error + name string + contextWithCancelFunc func() (context.Context, context.CancelFunc) + aggregatorFunc func(*gomock.Controller, context.CancelFunc) *Aggregator + unsignedMsg *avalancheWarp.UnsignedMessage + quorumNum uint64 + expectedSigners []*avalancheWarp.Validator + expectedErr error } tests := []test{ { - name: "can't get height", - contextFunc: context.Background, - aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + name: "can't get height", + contextWithCancelFunc: func() (context.Context, context.CancelFunc) { + return context.Background(), nil + }, + aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { state := validators.NewMockState(ctrl) state.EXPECT().GetCurrentHeight(gomock.Any()).Return(uint64(0), errTest) return New(subnetID, state, nil) @@ -99,9 +101,11 @@ func TestAggregateSignatures(t *testing.T) { expectedErr: errTest, }, { - name: "can't get validator set", - contextFunc: context.Background, - aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + name: "can't get validator set", + contextWithCancelFunc: func() (context.Context, context.CancelFunc) { + return context.Background(), nil + }, + aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { state := validators.NewMockState(ctrl) state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errTest) @@ -111,9 +115,11 @@ func TestAggregateSignatures(t *testing.T) { expectedErr: errTest, }, { - name: "no validators exist", - contextFunc: context.Background, - aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + name: "no validators exist", + contextWithCancelFunc: func() (context.Context, context.CancelFunc) { + return context.Background(), nil + }, + aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { state := validators.NewMockState(ctrl) state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil) @@ -124,9 +130,11 @@ func TestAggregateSignatures(t *testing.T) { expectedErr: errNoValidators, }, { - name: "0/3 validators reply with signature", - contextFunc: context.Background, - aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + name: "0/3 validators reply with signature", + contextWithCancelFunc: func() (context.Context, context.CancelFunc) { + return context.Background(), nil + }, + aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { state := validators.NewMockState(ctrl) state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( @@ -142,9 +150,11 @@ func TestAggregateSignatures(t *testing.T) { expectedErr: avalancheWarp.ErrInsufficientWeight, }, { - name: "1/3 validators reply with signature; insufficient weight", - contextFunc: context.Background, - aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + name: "1/3 validators reply with signature; insufficient weight", + contextWithCancelFunc: func() (context.Context, context.CancelFunc) { + return context.Background(), nil + }, + aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { state := validators.NewMockState(ctrl) state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( @@ -162,9 +172,11 @@ func TestAggregateSignatures(t *testing.T) { expectedErr: avalancheWarp.ErrInsufficientWeight, }, { - name: "2/3 validators reply with signature; insufficient weight", - contextFunc: context.Background, - aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + name: "2/3 validators reply with signature; insufficient weight", + contextWithCancelFunc: func() (context.Context, context.CancelFunc) { + return context.Background(), nil + }, + aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { state := validators.NewMockState(ctrl) state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( @@ -182,9 +194,11 @@ func TestAggregateSignatures(t *testing.T) { expectedErr: avalancheWarp.ErrInsufficientWeight, }, { - name: "2/3 validators reply with signature; sufficient weight", - contextFunc: context.Background, - aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + name: "2/3 validators reply with signature; sufficient weight", + contextWithCancelFunc: func() (context.Context, context.CancelFunc) { + return context.Background(), nil + }, + aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { state := validators.NewMockState(ctrl) state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( @@ -203,9 +217,11 @@ func TestAggregateSignatures(t *testing.T) { expectedErr: nil, }, { - name: "3/3 validators reply with signature; sufficient weight", - contextFunc: context.Background, - aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + name: "3/3 validators reply with signature; sufficient weight", + contextWithCancelFunc: func() (context.Context, context.CancelFunc) { + return context.Background(), nil + }, + aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { state := validators.NewMockState(ctrl) state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( @@ -224,9 +240,11 @@ func TestAggregateSignatures(t *testing.T) { expectedErr: nil, }, { - name: "3/3 validators reply with signature; 1 invalid signature; sufficient weight", - contextFunc: context.Background, - aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + name: "3/3 validators reply with signature; 1 invalid signature; sufficient weight", + contextWithCancelFunc: func() (context.Context, context.CancelFunc) { + return context.Background(), nil + }, + aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { state := validators.NewMockState(ctrl) state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( @@ -245,9 +263,11 @@ func TestAggregateSignatures(t *testing.T) { expectedErr: nil, }, { - name: "3/3 validators reply with signature; 3 invalid signatures; insufficient weight", - contextFunc: context.Background, - aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + name: "3/3 validators reply with signature; 3 invalid signatures; insufficient weight", + contextWithCancelFunc: func() (context.Context, context.CancelFunc) { + return context.Background(), nil + }, + aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { state := validators.NewMockState(ctrl) state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( @@ -265,9 +285,11 @@ func TestAggregateSignatures(t *testing.T) { expectedErr: avalancheWarp.ErrInsufficientWeight, }, { - name: "3/3 validators reply with signature; 2 invalid signatures; insufficient weight", - contextFunc: context.Background, - aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + name: "3/3 validators reply with signature; 2 invalid signatures; insufficient weight", + contextWithCancelFunc: func() (context.Context, context.CancelFunc) { + return context.Background(), nil + }, + aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { state := validators.NewMockState(ctrl) state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( @@ -285,9 +307,11 @@ func TestAggregateSignatures(t *testing.T) { expectedErr: avalancheWarp.ErrInsufficientWeight, }, { - name: "2/3 validators reply with signature; 1 invalid signature; sufficient weight", - contextFunc: context.Background, - aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + name: "2/3 validators reply with signature; 1 invalid signature; sufficient weight", + contextWithCancelFunc: func() (context.Context, context.CancelFunc) { + return context.Background(), nil + }, + aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { state := validators.NewMockState(ctrl) state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( @@ -307,12 +331,12 @@ func TestAggregateSignatures(t *testing.T) { }, { name: "early termination of signature fetching on parent context cancelation", - contextFunc: func() context.Context { + contextWithCancelFunc: func() (context.Context, context.CancelFunc) { ctx, cancel := context.WithCancel(context.Background()) cancel() - return ctx + return ctx, cancel }, - aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { state := validators.NewMockState(ctrl) state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( @@ -354,9 +378,57 @@ func TestAggregateSignatures(t *testing.T) { expectedErr: avalancheWarp.ErrInsufficientWeight, }, { - name: "early termination of signature fetching on passing threshold", - contextFunc: context.Background, - aggregatorFunc: func(ctrl *gomock.Controller) *Aggregator { + name: "context cancels halfway through signature fetching", + contextWithCancelFunc: func() (context.Context, context.CancelFunc) { + ctx, cancel := context.WithCancel(context.Background()) + return ctx, cancel + }, + aggregatorFunc: func(ctrl *gomock.Controller, cancel context.CancelFunc) *Aggregator { + state := validators.NewMockState(ctrl) + state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) + state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( + vdrSet, nil, + ) + + client := NewMockSignatureGetter(ctrl) + client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).DoAndReturn( + func(ctx context.Context, _ ids.NodeID, _ *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { + // cancel the context and return the signature + cancel() + return sig1, nil + }, + ) + client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).DoAndReturn( + func(ctx context.Context, _ ids.NodeID, _ *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { + // Should not be able to grab another signature since context was cancelled in another go routine + <-ctx.Done() + err := ctx.Err() + require.ErrorIs(t, err, context.Canceled) + return nil, err + }, + ) + client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).DoAndReturn( + func(ctx context.Context, _ ids.NodeID, _ *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { + // Should not be able to grab another signature since context was cancelled in another go routine + <-ctx.Done() + err := ctx.Err() + require.ErrorIs(t, err, context.Canceled) + return nil, err + }, + ) + return New(subnetID, state, client) + }, + unsignedMsg: unsignedMsg, + quorumNum: 33, // 1/3 Should have gotten one signature before cancellation + expectedSigners: []*avalancheWarp.Validator{vdr1}, + expectedErr: nil, + }, + { + name: "early termination of signature fetching on passing threshold", + contextWithCancelFunc: func() (context.Context, context.CancelFunc) { + return context.Background(), nil + }, + aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { state := validators.NewMockState(ctrl) state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( @@ -390,9 +462,14 @@ func TestAggregateSignatures(t *testing.T) { ctrl := gomock.NewController(t) require := require.New(t) - a := tt.aggregatorFunc(ctrl) + ctx, cancel := tt.contextWithCancelFunc() + // Guarantees that cancel is called preventing goroutine leak + if cancel != nil { + defer cancel() + } + a := tt.aggregatorFunc(ctrl, cancel) - res, err := a.AggregateSignatures(tt.contextFunc(), tt.unsignedMsg, tt.quorumNum) + res, err := a.AggregateSignatures(ctx, tt.unsignedMsg, tt.quorumNum) require.ErrorIs(err, tt.expectedErr) if err != nil { return From 722181291387c3dae18249a0dcaef1147db8a43f Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 5 Oct 2023 17:18:57 +0300 Subject: [PATCH 33/52] fix warp event packing (#939) --- plugin/evm/vm_warp_test.go | 2 +- tests/warp/warp_test.go | 2 +- x/warp/config.go | 2 +- x/warp/contract.go | 33 ++++++++++++++++++++++++++++----- x/warp/contract_test.go | 33 ++++++++++++++++++++++++++++++++- 5 files changed, 63 insertions(+), 9 deletions(-) diff --git a/plugin/evm/vm_warp_test.go b/plugin/evm/vm_warp_test.go index 99542949fb..0f6934f357 100644 --- a/plugin/evm/vm_warp_test.go +++ b/plugin/evm/vm_warp_test.go @@ -96,7 +96,7 @@ func TestSendWarpMessage(t *testing.T) { } require.Equal(expectedTopics, receipts[0].Logs[0].Topics) logData := receipts[0].Logs[0].Data - unsignedMessage, err := avalancheWarp.ParseUnsignedMessage(logData) + unsignedMessage, err := warp.UnpackSendWarpEventDataToMessage(logData) require.NoError(err) unsignedMessageID := unsignedMessage.ID() diff --git a/tests/warp/warp_test.go b/tests/warp/warp_test.go index 4b5894c91a..f0e5ca90e7 100644 --- a/tests/warp/warp_test.go +++ b/tests/warp/warp_test.go @@ -232,7 +232,7 @@ var _ = ginkgo.Describe("[Warp]", ginkgo.Ordered, func() { // the log extracted from the last block. txLog := logs[0] log.Info("Parsing logData as unsigned warp message") - unsignedMsg, err := avalancheWarp.ParseUnsignedMessage(txLog.Data) + unsignedMsg, err := warp.UnpackSendWarpEventDataToMessage(txLog.Data) gomega.Expect(err).Should(gomega.BeNil()) // Set local variables for the duration of the test diff --git a/x/warp/config.go b/x/warp/config.go index b76b97095b..669bb10797 100644 --- a/x/warp/config.go +++ b/x/warp/config.go @@ -104,7 +104,7 @@ func (c *Config) Equal(s precompileconfig.Config) bool { } func (c *Config) Accept(acceptCtx *precompileconfig.AcceptContext, txHash common.Hash, logIndex int, topics []common.Hash, logData []byte) error { - unsignedMessage, err := warp.ParseUnsignedMessage(logData) + unsignedMessage, err := UnpackSendWarpEventDataToMessage(logData) if err != nil { return fmt.Errorf("failed to parse warp log data into unsigned message (TxHash: %s, LogIndex: %d): %w", txHash, logIndex, err) } diff --git a/x/warp/contract.go b/x/warp/contract.go index 6f076a136e..f25e59cb54 100644 --- a/x/warp/contract.go +++ b/x/warp/contract.go @@ -76,6 +76,10 @@ type GetVerifiedWarpMessageOutput struct { Valid bool } +type SendWarpMessageEventData struct { + Message []byte +} + // PackGetBlockchainID packs the include selector (first 4 func signature bytes). // This function is mostly used for tests. func PackGetBlockchainID() ([]byte, error) { @@ -246,13 +250,17 @@ func sendWarpMessage(accessibleState contract.AccessibleState, caller common.Add } // Add a log to be handled if this action is finalized. + topics, data, err := PackSendWarpMessageEvent( + sourceAddress, + unsignedWarpMessage.Bytes(), + ) + if err != nil { + return nil, remainingGas, err + } accessibleState.GetStateDB().AddLog( ContractAddress, - []common.Hash{ - WarpABI.Events["SendWarpMessage"].ID, - sourceAddress.Hash(), - }, - unsignedWarpMessage.Bytes(), + topics, + data, accessibleState.GetBlockContext().Number().Uint64(), ) @@ -260,6 +268,21 @@ func sendWarpMessage(accessibleState contract.AccessibleState, caller common.Add return []byte{}, remainingGas, nil } +// PackSendWarpMessageEvent packs the given arguments into SendWarpMessage events including topics and data. +func PackSendWarpMessageEvent(sourceAddress common.Address, unsignedMessageBytes []byte) ([]common.Hash, []byte, error) { + return WarpABI.PackEvent("SendWarpMessage", sourceAddress, unsignedMessageBytes) +} + +// UnpackSendWarpEventDataToMessage attempts to unpack event [data] as warp.UnsignedMessage. +func UnpackSendWarpEventDataToMessage(data []byte) (*warp.UnsignedMessage, error) { + event := SendWarpMessageEventData{} + err := WarpABI.UnpackIntoInterface(&event, "SendWarpMessage", data) + if err != nil { + return nil, err + } + return warp.ParseUnsignedMessage(event.Message) +} + // createWarpPrecompile returns a StatefulPrecompiledContract with getters and setters for the precompile. func createWarpPrecompile() contract.StatefulPrecompiledContract { var functions []*contract.StatefulPrecompileFunction diff --git a/x/warp/contract_test.go b/x/warp/contract_test.go index 220adacd7d..68733c5ed6 100644 --- a/x/warp/contract_test.go +++ b/x/warp/contract_test.go @@ -12,6 +12,7 @@ import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/precompile/contract" @@ -133,7 +134,7 @@ func TestSendWarpMessage(t *testing.T) { require.Len(t, logsData, 1) logData := logsData[0] - unsignedWarpMsg, err := avalancheWarp.ParseUnsignedMessage(logData) + unsignedWarpMsg, err := UnpackSendWarpEventDataToMessage(logData) require.NoError(t, err) addressedPayload, err := warpPayload.ParseAddressedPayload(unsignedWarpMsg.Payload) require.NoError(t, err) @@ -676,3 +677,33 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { testutils.RunPrecompileTests(t, Module, state.NewTestStateDB, tests) } + +func TestPackEvents(t *testing.T) { + sourceChainID := ids.GenerateTestID() + sourceAddress := common.HexToAddress("0x0123") + payload := []byte("mcsorley") + networkID := uint32(54321) + + addressedPayload, err := warpPayload.NewAddressedPayload( + sourceAddress, + payload, + ) + require.NoError(t, err) + + unsignedWarpMessage, err := warp.NewUnsignedMessage( + networkID, + sourceChainID, + addressedPayload.Bytes(), + ) + require.NoError(t, err) + + _, data, err := PackSendWarpMessageEvent( + sourceAddress, + unsignedWarpMessage.Bytes(), + ) + require.NoError(t, err) + + unpacked, err := UnpackSendWarpEventDataToMessage(data) + require.NoError(t, err) + require.Equal(t, unsignedWarpMessage.Bytes(), unpacked.Bytes()) +} From e2874b033de4e354d1ba688db5ebf24d0774dcb1 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 6 Oct 2023 18:01:39 +0300 Subject: [PATCH 34/52] move setup to beforesuite (#940) --- tests/warp/warp_test.go | 103 ++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 58 deletions(-) diff --git a/tests/warp/warp_test.go b/tests/warp/warp_test.go index f0e5ca90e7..9e46670f8b 100644 --- a/tests/warp/warp_test.go +++ b/tests/warp/warp_test.go @@ -39,9 +39,20 @@ import ( const fundedKeyStr = "56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027" var ( - config = runner.NewDefaultANRConfig() - manager = runner.NewNetworkManager(config) - warpChainConfigPath string + config = runner.NewDefaultANRConfig() + manager = runner.NewNetworkManager(config) + warpChainConfigPath string + unsignedWarpMsg *avalancheWarp.UnsignedMessage + unsignedWarpMessageID ids.ID + signedWarpMsg *avalancheWarp.Message + blockchainIDA, blockchainIDB ids.ID + chainAURIs, chainBURIs []string + chainAWSClient, chainBWSClient ethclient.Client + chainID = big.NewInt(99999) + fundedKey *ecdsa.PrivateKey + fundedAddress common.Address + payload = []byte{1, 2, 3} + txSigner = types.LatestSignerForChainID(chainID) ) func TestE2E(t *testing.T) { @@ -108,7 +119,8 @@ var _ = ginkgo.BeforeSuite(func() { // Issue transactions to activate the proposerVM fork on the receiving chain chainID := big.NewInt(99999) - fundedKey, err := crypto.HexToECDSA(fundedKeyStr) + fundedKey, err = crypto.HexToECDSA(fundedKeyStr) + fundedAddress = crypto.PubkeyToAddress(fundedKey.PublicKey) gomega.Expect(err).Should(gomega.BeNil()) subnetB := manager.GetSubnets()[1] subnetBDetails, ok := manager.GetSubnet(subnetB) @@ -121,6 +133,35 @@ var _ = ginkgo.BeforeSuite(func() { err = utils.IssueTxsToActivateProposerVMFork(ctx, chainID, fundedKey, client) gomega.Expect(err).Should(gomega.BeNil()) + + subnetIDs := manager.GetSubnets() + gomega.Expect(len(subnetIDs)).Should(gomega.Equal(2)) + + subnetA := subnetIDs[0] + subnetADetails, ok := manager.GetSubnet(subnetA) + gomega.Expect(ok).Should(gomega.BeTrue()) + blockchainIDA = subnetADetails.BlockchainID + gomega.Expect(len(subnetADetails.ValidatorURIs)).Should(gomega.Equal(5)) + chainAURIs = append(chainAURIs, subnetADetails.ValidatorURIs...) + + subnetB = subnetIDs[1] + subnetBDetails, ok = manager.GetSubnet(subnetB) + gomega.Expect(ok).Should(gomega.BeTrue()) + blockchainIDB = subnetBDetails.BlockchainID + gomega.Expect(len(subnetBDetails.ValidatorURIs)).Should(gomega.Equal(5)) + chainBURIs = append(chainBURIs, subnetBDetails.ValidatorURIs...) + + log.Info("Created URIs for both subnets", "ChainAURIs", chainAURIs, "ChainBURIs", chainBURIs, "blockchainIDA", blockchainIDA, "blockchainIDB", blockchainIDB) + + chainAWSURI := toWebsocketURI(chainAURIs[0], blockchainIDA.String()) + log.Info("Creating ethclient for blockchainA", "wsURI", chainAWSURI) + chainAWSClient, err = ethclient.Dial(chainAWSURI) + gomega.Expect(err).Should(gomega.BeNil()) + + chainBWSURI := toWebsocketURI(chainBURIs[0], blockchainIDB.String()) + log.Info("Creating ethclient for blockchainB", "wsURI", chainBWSURI) + chainBWSClient, err = ethclient.Dial(chainBWSURI) + gomega.Expect(err).Should(gomega.BeNil()) }) var _ = ginkgo.AfterSuite(func() { @@ -131,64 +172,10 @@ var _ = ginkgo.AfterSuite(func() { }) var _ = ginkgo.Describe("[Warp]", ginkgo.Ordered, func() { - var ( - unsignedWarpMsg *avalancheWarp.UnsignedMessage - unsignedWarpMessageID ids.ID - signedWarpMsg *avalancheWarp.Message - blockchainIDA, blockchainIDB ids.ID - chainAURIs, chainBURIs []string - chainAWSClient, chainBWSClient ethclient.Client - chainID = big.NewInt(99999) - fundedKey *ecdsa.PrivateKey - fundedAddress common.Address - payload = []byte{1, 2, 3} - txSigner = types.LatestSignerForChainID(chainID) - err error - ) - - fundedKey, err = crypto.HexToECDSA(fundedKeyStr) - if err != nil { - panic(err) - } - fundedAddress = crypto.PubkeyToAddress(fundedKey.PublicKey) - - ginkgo.It("Setup URIs", ginkgo.Label("Warp", "SetupWarp"), func() { - subnetIDs := manager.GetSubnets() - gomega.Expect(len(subnetIDs)).Should(gomega.Equal(2)) - - subnetA := subnetIDs[0] - subnetADetails, ok := manager.GetSubnet(subnetA) - gomega.Expect(ok).Should(gomega.BeTrue()) - blockchainIDA = subnetADetails.BlockchainID - gomega.Expect(len(subnetADetails.ValidatorURIs)).Should(gomega.Equal(5)) - chainAURIs = append(chainAURIs, subnetADetails.ValidatorURIs...) - - subnetB := subnetIDs[1] - subnetBDetails, ok := manager.GetSubnet(subnetB) - gomega.Expect(ok).Should(gomega.BeTrue()) - blockchainIDB = subnetBDetails.BlockchainID - gomega.Expect(len(subnetBDetails.ValidatorURIs)).Should(gomega.Equal(5)) - chainBURIs = append(chainBURIs, subnetBDetails.ValidatorURIs...) - - log.Info("Created URIs for both subnets", "ChainAURIs", chainAURIs, "ChainBURIs", chainBURIs, "blockchainIDA", blockchainIDA, "blockchainIDB", blockchainIDB) - - chainAWSURI := toWebsocketURI(chainAURIs[0], blockchainIDA.String()) - log.Info("Creating ethclient for blockchainA", "wsURI", chainAWSURI) - chainAWSClient, err = ethclient.Dial(chainAWSURI) - gomega.Expect(err).Should(gomega.BeNil()) - - chainBWSURI := toWebsocketURI(chainBURIs[0], blockchainIDB.String()) - log.Info("Creating ethclient for blockchainB", "wsURI", chainBWSURI) - chainBWSClient, err = ethclient.Dial(chainBWSURI) - gomega.Expect(err).Should(gomega.BeNil()) - }) - // Send a transaction to Subnet A to issue a Warp Message to Subnet B ginkgo.It("Send Message from A to B", ginkgo.Label("Warp", "SendWarp"), func() { ctx := context.Background() - gomega.Expect(err).Should(gomega.BeNil()) - log.Info("Subscribing to new heads") newHeads := make(chan *types.Header, 10) sub, err := chainAWSClient.SubscribeNewHead(ctx, newHeads) From c42d85b488de06db0c0f00c006014aa85669fa44 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 6 Oct 2023 12:30:29 -0700 Subject: [PATCH 35/52] warp: use bitset in predicate results to encode indexes of failures instead of success (#942) * warp: use 1 in result bitset for fails * t.Fatal -> require.NoError --- x/warp/config.go | 5 +- x/warp/contract_test.go | 122 ++++++++++++++++++++------------ x/warp/contract_warp_handler.go | 2 +- x/warp/predicate_test.go | 20 +++--- 4 files changed, 92 insertions(+), 57 deletions(-) diff --git a/x/warp/config.go b/x/warp/config.go index 669bb10797..a5bd8543ce 100644 --- a/x/warp/config.go +++ b/x/warp/config.go @@ -197,12 +197,13 @@ func (c *Config) verifyPredicate(predicateContext *precompileconfig.PredicateCon return c.verifyWarpMessage(predicateContext, warpMessage) } -// VerifyPredicate verifies the predicate represents a valid signed and properly formatted Avalanche Warp Message. +// VerifyPredicate computes indices of predicates that failed verification as a bitset then returns the result +// as a byte slice. func (c *Config) VerifyPredicate(predicateContext *precompileconfig.PredicateContext, predicates [][]byte) []byte { resultBitSet := set.NewBits() for predicateIndex, predicateBytes := range predicates { - if c.verifyPredicate(predicateContext, predicateBytes) { + if !c.verifyPredicate(predicateContext, predicateBytes) { resultBitSet.Add(predicateIndex) } } diff --git a/x/warp/contract_test.go b/x/warp/contract_test.go index 68733c5ed6..02eeeebbf9 100644 --- a/x/warp/contract_test.go +++ b/x/warp/contract_test.go @@ -167,6 +167,8 @@ func TestGetVerifiedWarpMessage(t *testing.T) { warpMessagePredicateBytes := predicate.PackPredicate(warpMessage.Bytes()) getVerifiedWarpMsg, err := PackGetVerifiedWarpMessage(0) require.NoError(t, err) + noFailures := set.NewBits().Bytes() + require.Len(t, noFailures, 0) tests := map[string]testutils.PrecompileTest{ "get message success": { @@ -176,7 +178,7 @@ func TestGetVerifiedWarpMessage(t *testing.T) { state.SetPredicateStorageSlots(ContractAddress, [][]byte{warpMessagePredicateBytes}) }, SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(0).Bytes()) + mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(noFailures) }, SuppliedGas: GetVerifiedWarpMessageBaseCost + GasCostPerWarpMessageBytes*uint64(len(warpMessagePredicateBytes)), ReadOnly: false, @@ -199,16 +201,14 @@ func TestGetVerifiedWarpMessage(t *testing.T) { Caller: callerAddr, InputFn: func(t testing.TB) []byte { input, err := PackGetVerifiedWarpMessage(1) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) return input }, BeforeHook: func(t testing.TB, state contract.StateDB) { state.SetPredicateStorageSlots(ContractAddress, [][]byte{warpMessagePredicateBytes}) }, SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits().Bytes()) + mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(noFailures) }, SuppliedGas: GetVerifiedWarpMessageBaseCost, ReadOnly: false, @@ -224,16 +224,14 @@ func TestGetVerifiedWarpMessage(t *testing.T) { Caller: callerAddr, InputFn: func(t testing.TB) []byte { input, err := PackGetVerifiedWarpMessage(1) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) return input }, BeforeHook: func(t testing.TB, state contract.StateDB) { state.SetPredicateStorageSlots(ContractAddress, [][]byte{{}, warpMessagePredicateBytes}) }, SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(1).Bytes()) + mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(0).Bytes()) }, SuppliedGas: GetVerifiedWarpMessageBaseCost + GasCostPerWarpMessageBytes*uint64(len(warpMessagePredicateBytes)), ReadOnly: false, @@ -252,11 +250,34 @@ func TestGetVerifiedWarpMessage(t *testing.T) { return res }(), }, + "get message failure non-zero index": { + Caller: callerAddr, + InputFn: func(t testing.TB) []byte { + input, err := PackGetVerifiedWarpMessage(1) + require.NoError(t, err) + return input + }, + BeforeHook: func(t testing.TB, state contract.StateDB) { + state.SetPredicateStorageSlots(ContractAddress, [][]byte{{}, warpMessagePredicateBytes}) + }, + SetupBlockContext: func(mbc *contract.MockBlockContext) { + mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(0, 1).Bytes()) + }, + SuppliedGas: GetVerifiedWarpMessageBaseCost, + ReadOnly: false, + ExpectedRes: func() []byte { + res, err := PackGetVerifiedWarpMessageOutput(GetVerifiedWarpMessageOutput{Valid: false}) + if err != nil { + panic(err) + } + return res + }(), + }, "get non-existent message": { Caller: callerAddr, InputFn: func(t testing.TB) []byte { return getVerifiedWarpMsg }, SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits().Bytes()) + mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(noFailures) }, SuppliedGas: GetVerifiedWarpMessageBaseCost, ReadOnly: false, @@ -275,7 +296,7 @@ func TestGetVerifiedWarpMessage(t *testing.T) { state.SetPredicateStorageSlots(ContractAddress, [][]byte{warpMessagePredicateBytes}) }, SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(0).Bytes()) + mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(noFailures) }, SuppliedGas: GetVerifiedWarpMessageBaseCost + GasCostPerWarpMessageBytes*uint64(len(warpMessagePredicateBytes)), ReadOnly: true, @@ -298,7 +319,7 @@ func TestGetVerifiedWarpMessage(t *testing.T) { Caller: callerAddr, InputFn: func(t testing.TB) []byte { return getVerifiedWarpMsg }, SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits().Bytes()) + mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(noFailures) }, SuppliedGas: GetVerifiedWarpMessageBaseCost, ReadOnly: true, @@ -327,7 +348,7 @@ func TestGetVerifiedWarpMessage(t *testing.T) { state.SetPredicateStorageSlots(ContractAddress, [][]byte{warpMessagePredicateBytes}) }, SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(0).Bytes()) + mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(noFailures) }, SuppliedGas: GetVerifiedWarpMessageBaseCost + GasCostPerWarpMessageBytes*uint64(len(warpMessagePredicateBytes)) - 1, ReadOnly: false, @@ -340,7 +361,7 @@ func TestGetVerifiedWarpMessage(t *testing.T) { state.SetPredicateStorageSlots(ContractAddress, [][]byte{warpMessage.Bytes()}) }, SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(0).Bytes()) + mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(noFailures) }, SuppliedGas: GetVerifiedWarpMessageBaseCost + GasCostPerWarpMessageBytes*uint64(len(warpMessage.Bytes())), ReadOnly: false, @@ -353,7 +374,7 @@ func TestGetVerifiedWarpMessage(t *testing.T) { state.SetPredicateStorageSlots(ContractAddress, [][]byte{predicate.PackPredicate([]byte{1, 2, 3})}) }, SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(0).Bytes()) + mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(noFailures) }, SuppliedGas: GetVerifiedWarpMessageBaseCost + GasCostPerWarpMessageBytes*uint64(32), ReadOnly: false, @@ -371,7 +392,7 @@ func TestGetVerifiedWarpMessage(t *testing.T) { state.SetPredicateStorageSlots(ContractAddress, [][]byte{predicate.PackPredicate(warpMessage.Bytes())}) }, SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(0).Bytes()) + mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(noFailures) }, SuppliedGas: GetVerifiedWarpMessageBaseCost + GasCostPerWarpMessageBytes*uint64(160), ReadOnly: false, @@ -390,9 +411,7 @@ func TestGetVerifiedWarpMessage(t *testing.T) { Caller: callerAddr, InputFn: func(t testing.TB) []byte { res, err := PackGetVerifiedWarpMessage(math.MaxInt32 + 1) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) return res }, SuppliedGas: GetVerifiedWarpMessageBaseCost, @@ -403,9 +422,7 @@ func TestGetVerifiedWarpMessage(t *testing.T) { Caller: callerAddr, InputFn: func(t testing.TB) []byte { res, err := PackGetVerifiedWarpMessage(1) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) return res[:len(res)-2] }, SuppliedGas: GetVerifiedWarpMessageBaseCost, @@ -431,6 +448,8 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { warpMessagePredicateBytes := predicate.PackPredicate(warpMessage.Bytes()) getVerifiedWarpBlockHash, err := PackGetVerifiedWarpBlockHash(0) require.NoError(t, err) + noFailures := set.NewBits().Bytes() + require.Len(t, noFailures, 0) tests := map[string]testutils.PrecompileTest{ "get message success": { @@ -440,7 +459,7 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { state.SetPredicateStorageSlots(ContractAddress, [][]byte{warpMessagePredicateBytes}) }, SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(0).Bytes()) + mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(noFailures) }, SuppliedGas: GetVerifiedWarpMessageBaseCost + GasCostPerWarpMessageBytes*uint64(len(warpMessagePredicateBytes)), ReadOnly: false, @@ -462,16 +481,14 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { Caller: callerAddr, InputFn: func(t testing.TB) []byte { input, err := PackGetVerifiedWarpBlockHash(1) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) return input }, BeforeHook: func(t testing.TB, state contract.StateDB) { state.SetPredicateStorageSlots(ContractAddress, [][]byte{warpMessagePredicateBytes}) }, SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits().Bytes()) + mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(noFailures) }, SuppliedGas: GetVerifiedWarpMessageBaseCost, ReadOnly: false, @@ -487,16 +504,14 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { Caller: callerAddr, InputFn: func(t testing.TB) []byte { input, err := PackGetVerifiedWarpBlockHash(1) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) return input }, BeforeHook: func(t testing.TB, state contract.StateDB) { state.SetPredicateStorageSlots(ContractAddress, [][]byte{{}, warpMessagePredicateBytes}) }, SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(1).Bytes()) + mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(0).Bytes()) }, SuppliedGas: GetVerifiedWarpMessageBaseCost + GasCostPerWarpMessageBytes*uint64(len(warpMessagePredicateBytes)), ReadOnly: false, @@ -514,11 +529,34 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { return res }(), }, + "get message failure non-zero index": { + Caller: callerAddr, + InputFn: func(t testing.TB) []byte { + input, err := PackGetVerifiedWarpBlockHash(1) + require.NoError(t, err) + return input + }, + BeforeHook: func(t testing.TB, state contract.StateDB) { + state.SetPredicateStorageSlots(ContractAddress, [][]byte{{}, warpMessagePredicateBytes}) + }, + SetupBlockContext: func(mbc *contract.MockBlockContext) { + mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(0, 1).Bytes()) + }, + SuppliedGas: GetVerifiedWarpMessageBaseCost, + ReadOnly: false, + ExpectedRes: func() []byte { + res, err := PackGetVerifiedWarpBlockHashOutput(GetVerifiedWarpBlockHashOutput{Valid: false}) + if err != nil { + panic(err) + } + return res + }(), + }, "get non-existent message": { Caller: callerAddr, InputFn: func(t testing.TB) []byte { return getVerifiedWarpBlockHash }, SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits().Bytes()) + mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(noFailures) }, SuppliedGas: GetVerifiedWarpMessageBaseCost, ReadOnly: false, @@ -537,7 +575,7 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { state.SetPredicateStorageSlots(ContractAddress, [][]byte{warpMessagePredicateBytes}) }, SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(0).Bytes()) + mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(noFailures) }, SuppliedGas: GetVerifiedWarpMessageBaseCost + GasCostPerWarpMessageBytes*uint64(len(warpMessagePredicateBytes)), ReadOnly: true, @@ -559,7 +597,7 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { Caller: callerAddr, InputFn: func(t testing.TB) []byte { return getVerifiedWarpBlockHash }, SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits().Bytes()) + mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(noFailures) }, SuppliedGas: GetVerifiedWarpMessageBaseCost, ReadOnly: true, @@ -588,7 +626,7 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { state.SetPredicateStorageSlots(ContractAddress, [][]byte{warpMessagePredicateBytes}) }, SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(0).Bytes()) + mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(noFailures) }, SuppliedGas: GetVerifiedWarpMessageBaseCost + GasCostPerWarpMessageBytes*uint64(len(warpMessagePredicateBytes)) - 1, ReadOnly: false, @@ -601,7 +639,7 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { state.SetPredicateStorageSlots(ContractAddress, [][]byte{warpMessage.Bytes()}) }, SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(0).Bytes()) + mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(noFailures) }, SuppliedGas: GetVerifiedWarpMessageBaseCost + GasCostPerWarpMessageBytes*uint64(len(warpMessage.Bytes())), ReadOnly: false, @@ -614,7 +652,7 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { state.SetPredicateStorageSlots(ContractAddress, [][]byte{predicate.PackPredicate([]byte{1, 2, 3})}) }, SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(0).Bytes()) + mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(noFailures) }, SuppliedGas: GetVerifiedWarpMessageBaseCost + GasCostPerWarpMessageBytes*uint64(32), ReadOnly: false, @@ -632,7 +670,7 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { state.SetPredicateStorageSlots(ContractAddress, [][]byte{predicate.PackPredicate(warpMessage.Bytes())}) }, SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(set.NewBits(0).Bytes()) + mbc.EXPECT().GetPredicateResults(common.Hash{}, ContractAddress).Return(noFailures) }, SuppliedGas: GetVerifiedWarpMessageBaseCost + GasCostPerWarpMessageBytes*uint64(160), ReadOnly: false, @@ -651,9 +689,7 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { Caller: callerAddr, InputFn: func(t testing.TB) []byte { res, err := PackGetVerifiedWarpBlockHash(math.MaxInt32 + 1) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) return res }, SuppliedGas: GetVerifiedWarpMessageBaseCost, @@ -664,9 +700,7 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { Caller: callerAddr, InputFn: func(t testing.TB) []byte { res, err := PackGetVerifiedWarpBlockHash(1) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) return res[:len(res)-2] }, SuppliedGas: GetVerifiedWarpMessageBaseCost, diff --git a/x/warp/contract_warp_handler.go b/x/warp/contract_warp_handler.go index 18792a3df0..02d58aa433 100644 --- a/x/warp/contract_warp_handler.go +++ b/x/warp/contract_warp_handler.go @@ -62,7 +62,7 @@ func handleWarpMessage(accessibleState contract.AccessibleState, input []byte, s state := accessibleState.GetStateDB() predicateBytes, exists := state.GetPredicateStorageSlots(ContractAddress, warpIndex) predicateResults := accessibleState.GetBlockContext().GetPredicateResults(state.GetTxHash(), ContractAddress) - valid := exists && set.BitsFromBytes(predicateResults).Contains(warpIndex) + valid := exists && !set.BitsFromBytes(predicateResults).Contains(warpIndex) if !valid { return handler.packFailed(), remainingGas, nil } diff --git a/x/warp/predicate_test.go b/x/warp/predicate_test.go index 91cb4ca911..a5f5557fe1 100644 --- a/x/warp/predicate_test.go +++ b/x/warp/predicate_test.go @@ -223,7 +223,7 @@ func createValidPredicateTest(snowCtx *snow.Context, numKeys uint64, predicateBy StorageSlots: [][]byte{predicateBytes}, Gas: GasCostPerSignatureVerification + uint64(len(predicateBytes))*GasCostPerWarpMessageBytes + numKeys*GasCostPerWarpSigner, GasErr: nil, - PredicateRes: set.NewBits(0).Bytes(), + PredicateRes: set.NewBits().Bytes(), } } @@ -287,7 +287,7 @@ func TestWarpMessageFromPrimaryNetwork(t *testing.T) { StorageSlots: [][]byte{predicateBytes}, Gas: GasCostPerSignatureVerification + uint64(len(predicateBytes))*GasCostPerWarpMessageBytes + uint64(numKeys)*GasCostPerWarpSigner, GasErr: nil, - PredicateRes: set.NewBits(0).Bytes(), + PredicateRes: set.NewBits().Bytes(), } test.Run(t) @@ -393,7 +393,7 @@ func TestInvalidAddressedPayload(t *testing.T) { StorageSlots: [][]byte{predicateBytes}, Gas: GasCostPerSignatureVerification + uint64(len(predicateBytes))*GasCostPerWarpMessageBytes + uint64(numKeys)*GasCostPerWarpSigner, GasErr: nil, - PredicateRes: set.NewBits().Bytes(), + PredicateRes: set.NewBits(0).Bytes(), } test.Run(t) @@ -467,9 +467,9 @@ func TestWarpSignatureWeightsDefaultQuorumNumerator(t *testing.T) { predicateBytes = createPredicate(numSigners) expectedPredicateResults = set.NewBits() ) - // If the number of signers is greater than the required numerator and does not exceed the denominator, then - // mark the expected result as valid. - if numSigners >= int(params.WarpDefaultQuorumNumerator) && numSigners <= int(params.WarpQuorumDenominator) { + // If the number of signers is less than the required numerator or exceeds the denominator, then + // mark the expected result as invalid. + if numSigners < int(params.WarpDefaultQuorumNumerator) || numSigners > int(params.WarpQuorumDenominator) { expectedPredicateResults.Add(0) } tests[fmt.Sprintf("default quorum %d signature(s)", numSigners)] = testutils.PredicateTest{ @@ -518,10 +518,10 @@ func TestWarpMultiplePredicates(t *testing.T) { expectedGas := uint64(0) for index, valid := range validMessageIndices { if valid { - expectedPredicateResults.Add(index) predicates[index] = common.CopyBytes(validPredicateBytes) expectedGas += GasCostPerSignatureVerification + uint64(len(validPredicateBytes))*GasCostPerWarpMessageBytes + uint64(numSigners)*GasCostPerWarpSigner } else { + expectedPredicateResults.Add(index) expectedGas += GasCostPerSignatureVerification + uint64(len(invalidPredicateBytes))*GasCostPerWarpMessageBytes + uint64(1)*GasCostPerWarpSigner predicates[index] = invalidPredicateBytes } @@ -564,9 +564,9 @@ func TestWarpSignatureWeightsNonDefaultQuorumNumerator(t *testing.T) { predicateBytes = createPredicate(numSigners) expectedPredicateResults = set.NewBits() ) - // If the number of signers is greater than the required numerator and does not exceed the denominator, then - // mark the expected result as valid. - if numSigners >= nonDefaultQuorumNumerator && numSigners <= int(params.WarpQuorumDenominator) { + // If the number of signers is less than the required numerator or exceeds the denominator, then + // mark the expected result as invalid. + if numSigners < nonDefaultQuorumNumerator || numSigners > int(params.WarpQuorumDenominator) { expectedPredicateResults.Add(0) } name := fmt.Sprintf("non-default quorum %d signature(s)", numSigners) From a43938171537b6851901e8d3c4041fde972f6a78 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 11 Oct 2023 01:38:07 +0300 Subject: [PATCH 36/52] remove flaky flag from golangbindings test (#951) --- accounts/abi/bind/bind_test.go | 1 - accounts/abi/bind/precompilebind/precompile_bind_test.go | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 049c77b2c5..92aa0d04be 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -2063,7 +2063,6 @@ func TestGolangBindingsOverload(t *testing.T) { } func TestGolangBindings(t *testing.T) { - t.Skip("FLAKY") golangBindings(t, false) } diff --git a/accounts/abi/bind/precompilebind/precompile_bind_test.go b/accounts/abi/bind/precompilebind/precompile_bind_test.go index cffeb79c06..7778c46b25 100644 --- a/accounts/abi/bind/precompilebind/precompile_bind_test.go +++ b/accounts/abi/bind/precompilebind/precompile_bind_test.go @@ -514,7 +514,7 @@ func TestPrecompileBind(t *testing.T) { // Create a temporary workspace for the test suite ws := t.TempDir() - pkg := filepath.Join(ws, "bindtest") + pkg := filepath.Join(ws, "precompilebindtest") if err := os.MkdirAll(pkg, 0o700); err != nil { t.Fatalf("failed to create package: %v", err) } @@ -581,7 +581,7 @@ func TestPrecompileBind(t *testing.T) { }) } - moder := exec.Command(gocmd, "mod", "init", "bindtest") + moder := exec.Command(gocmd, "mod", "init", "precompilebindtest") moder.Dir = pkg if out, err := moder.CombinedOutput(); err != nil { t.Fatalf("failed to convert binding test to modules: %v\n%s", err, out) From f02f323c0eea0c25ab7a7d5558a6b903a6738fc6 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Wed, 11 Oct 2023 09:53:28 -0400 Subject: [PATCH 37/52] warp: refactor warp handler stats (#944) * warp: refactor warp handler stats * warp: update handler stats var name to signatureRequestDuration --------- Co-authored-by: Ceyhun Onur --- plugin/evm/network_handler.go | 5 +- warp/handlers/signature_request.go | 21 ++---- warp/handlers/signature_request_test.go | 37 +++++----- warp/handlers/stats.go | 40 +++++++++++ warp/handlers/stats/stats.go | 91 ------------------------- 5 files changed, 66 insertions(+), 128 deletions(-) create mode 100644 warp/handlers/stats.go delete mode 100644 warp/handlers/stats/stats.go diff --git a/plugin/evm/network_handler.go b/plugin/evm/network_handler.go index 0acee2599d..f0d68bc4d6 100644 --- a/plugin/evm/network_handler.go +++ b/plugin/evm/network_handler.go @@ -16,7 +16,6 @@ import ( "github.com/ava-labs/subnet-evm/trie" "github.com/ava-labs/subnet-evm/warp" warpHandlers "github.com/ava-labs/subnet-evm/warp/handlers" - warpStats "github.com/ava-labs/subnet-evm/warp/handlers/stats" ) var _ message.RequestHandler = &networkHandler{} @@ -25,7 +24,7 @@ type networkHandler struct { stateTrieLeafsRequestHandler *syncHandlers.LeafsRequestHandler blockRequestHandler *syncHandlers.BlockRequestHandler codeRequestHandler *syncHandlers.CodeRequestHandler - signatureRequestHandler warpHandlers.SignatureRequestHandler + signatureRequestHandler *warpHandlers.SignatureRequestHandler } // newNetworkHandler constructs the handler for serving network requests. @@ -41,7 +40,7 @@ func newNetworkHandler( stateTrieLeafsRequestHandler: syncHandlers.NewLeafsRequestHandler(evmTrieDB, provider, networkCodec, syncStats), blockRequestHandler: syncHandlers.NewBlockRequestHandler(provider, networkCodec, syncStats), codeRequestHandler: syncHandlers.NewCodeRequestHandler(diskDB, networkCodec, syncStats), - signatureRequestHandler: warpHandlers.NewSignatureRequestHandler(warpBackend, networkCodec, warpStats.NewStats()), + signatureRequestHandler: warpHandlers.NewSignatureRequestHandler(warpBackend, networkCodec), } } diff --git a/warp/handlers/signature_request.go b/warp/handlers/signature_request.go index 45745a9993..30be346327 100644 --- a/warp/handlers/signature_request.go +++ b/warp/handlers/signature_request.go @@ -12,28 +12,21 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/subnet-evm/plugin/evm/message" "github.com/ava-labs/subnet-evm/warp" - "github.com/ava-labs/subnet-evm/warp/handlers/stats" "github.com/ethereum/go-ethereum/log" ) -// SignatureRequestHandler is a peer.RequestHandler for message.SignatureRequest -// serving requested BLS signature data -type SignatureRequestHandler interface { - OnSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest message.SignatureRequest) ([]byte, error) -} - -// signatureRequestHandler implements the SignatureRequestHandler interface -type signatureRequestHandler struct { +// SignatureRequestHandler serves warp signature requests. It is a peer.RequestHandler for message.SignatureRequest. +type SignatureRequestHandler struct { backend warp.Backend codec codec.Manager - stats stats.SignatureRequestHandlerStats + stats *handlerStats } -func NewSignatureRequestHandler(backend warp.Backend, codec codec.Manager, stats stats.SignatureRequestHandlerStats) SignatureRequestHandler { - return &signatureRequestHandler{ +func NewSignatureRequestHandler(backend warp.Backend, codec codec.Manager) *SignatureRequestHandler { + return &SignatureRequestHandler{ backend: backend, codec: codec, - stats: stats, + stats: newStats(), } } @@ -42,7 +35,7 @@ func NewSignatureRequestHandler(backend warp.Backend, codec codec.Manager, stats // Expects returned errors to be treated as FATAL // Returns empty response if signature is not found // Assumes ctx is active -func (s *signatureRequestHandler) OnSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest message.SignatureRequest) ([]byte, error) { +func (s *SignatureRequestHandler) OnSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest message.SignatureRequest) ([]byte, error) { startTime := time.Now() s.stats.IncSignatureRequest() diff --git a/warp/handlers/signature_request_test.go b/warp/handlers/signature_request_test.go index 47a5c77806..0d4a1fb373 100644 --- a/warp/handlers/signature_request_test.go +++ b/warp/handlers/signature_request_test.go @@ -15,7 +15,6 @@ import ( avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/subnet-evm/plugin/evm/message" "github.com/ava-labs/subnet-evm/warp" - "github.com/ava-labs/subnet-evm/warp/handlers/stats" "github.com/stretchr/testify/require" ) @@ -38,12 +37,10 @@ func TestSignatureHandler(t *testing.T) { unknownMessageID := ids.GenerateTestID() emptySignature := [bls.SignatureLen]byte{} - mockHandlerStats := &stats.MockSignatureRequestHandlerStats{} - signatureRequestHandler := NewSignatureRequestHandler(backend, message.Codec, mockHandlerStats) tests := map[string]struct { setup func() (request message.SignatureRequest, expectedResponse []byte) - verifyStats func(t *testing.T, stats *stats.MockSignatureRequestHandlerStats) + verifyStats func(t *testing.T, stats *handlerStats) }{ "normal": { setup: func() (request message.SignatureRequest, expectedResponse []byte) { @@ -51,11 +48,11 @@ func TestSignatureHandler(t *testing.T) { MessageID: messageID, }, signature[:] }, - verifyStats: func(t *testing.T, stats *stats.MockSignatureRequestHandlerStats) { - require.EqualValues(t, 1, mockHandlerStats.SignatureRequestCount) - require.EqualValues(t, 1, mockHandlerStats.SignatureRequestHit) - require.EqualValues(t, 0, mockHandlerStats.SignatureRequestMiss) - require.Greater(t, mockHandlerStats.SignatureRequestDuration, time.Duration(0)) + verifyStats: func(t *testing.T, stats *handlerStats) { + require.EqualValues(t, 1, stats.signatureRequest.Count()) + require.EqualValues(t, 1, stats.signatureHit.Count()) + require.EqualValues(t, 0, stats.signatureMiss.Count()) + require.Greater(t, stats.signatureRequestDuration.Value(), time.Duration(0)) }, }, "unknown": { @@ -64,27 +61,28 @@ func TestSignatureHandler(t *testing.T) { MessageID: unknownMessageID, }, emptySignature[:] }, - verifyStats: func(t *testing.T, stats *stats.MockSignatureRequestHandlerStats) { - require.EqualValues(t, 1, mockHandlerStats.SignatureRequestCount) - require.EqualValues(t, 1, mockHandlerStats.SignatureRequestMiss) - require.EqualValues(t, 0, mockHandlerStats.SignatureRequestHit) - require.Greater(t, mockHandlerStats.SignatureRequestDuration, time.Duration(0)) + verifyStats: func(t *testing.T, stats *handlerStats) { + require.EqualValues(t, 1, stats.signatureRequest.Count()) + require.EqualValues(t, 0, stats.signatureHit.Count()) + require.EqualValues(t, 1, stats.signatureMiss.Count()) + require.Greater(t, stats.signatureRequestDuration.Value(), time.Duration(0)) }, }, } for name, test := range tests { - // Reset stats before each test - mockHandlerStats.Reset() - t.Run(name, func(t *testing.T) { + handler := NewSignatureRequestHandler(backend, message.Codec) + handler.stats.Clear() + request, expectedResponse := test.setup() - responseBytes, err := signatureRequestHandler.OnSignatureRequest(context.Background(), ids.GenerateTestNodeID(), 1, request) + responseBytes, err := handler.OnSignatureRequest(context.Background(), ids.GenerateTestNodeID(), 1, request) require.NoError(t, err) + test.verifyStats(t, handler.stats) + // If the expected response is empty, assert that the handler returns an empty response and return early. if len(expectedResponse) == 0 { - test.verifyStats(t, mockHandlerStats) require.Len(t, responseBytes, 0, "expected response to be empty") return } @@ -93,7 +91,6 @@ func TestSignatureHandler(t *testing.T) { require.NoError(t, err, "error unmarshalling SignatureResponse") require.Equal(t, expectedResponse, response.Signature[:]) - test.verifyStats(t, mockHandlerStats) }) } } diff --git a/warp/handlers/stats.go b/warp/handlers/stats.go new file mode 100644 index 0000000000..dfb261f9a3 --- /dev/null +++ b/warp/handlers/stats.go @@ -0,0 +1,40 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package handlers + +import ( + "time" + + "github.com/ava-labs/subnet-evm/metrics" +) + +type handlerStats struct { + // SignatureRequestHandler metrics + signatureRequest metrics.Counter + signatureHit metrics.Counter + signatureMiss metrics.Counter + signatureRequestDuration metrics.Gauge +} + +func newStats() *handlerStats { + return &handlerStats{ + signatureRequest: metrics.GetOrRegisterCounter("signature_request_count", nil), + signatureHit: metrics.GetOrRegisterCounter("signature_request_hit", nil), + signatureMiss: metrics.GetOrRegisterCounter("signature_request_miss", nil), + signatureRequestDuration: metrics.GetOrRegisterGauge("signature_request_duration", nil), + } +} + +func (h *handlerStats) IncSignatureRequest() { h.signatureRequest.Inc(1) } +func (h *handlerStats) IncSignatureHit() { h.signatureHit.Inc(1) } +func (h *handlerStats) IncSignatureMiss() { h.signatureMiss.Inc(1) } +func (h *handlerStats) UpdateSignatureRequestTime(duration time.Duration) { + h.signatureRequestDuration.Inc(int64(duration)) +} +func (h *handlerStats) Clear() { + h.signatureRequest.Clear() + h.signatureHit.Clear() + h.signatureMiss.Clear() + h.signatureRequestDuration.Update(0) +} diff --git a/warp/handlers/stats/stats.go b/warp/handlers/stats/stats.go deleted file mode 100644 index dab8be34f8..0000000000 --- a/warp/handlers/stats/stats.go +++ /dev/null @@ -1,91 +0,0 @@ -// (c) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package stats - -import ( - "sync" - "time" - - "github.com/ava-labs/subnet-evm/metrics" -) - -var ( - _ SignatureRequestHandlerStats = (*handlerStats)(nil) - _ SignatureRequestHandlerStats = (*MockSignatureRequestHandlerStats)(nil) -) - -type SignatureRequestHandlerStats interface { - IncSignatureRequest() - IncSignatureHit() - IncSignatureMiss() - UpdateSignatureRequestTime(duration time.Duration) -} - -type handlerStats struct { - // SignatureRequestHandler metrics - signatureRequest metrics.Counter - signatureHit metrics.Counter - signatureMiss metrics.Counter - signatureProcessingTime metrics.Timer -} - -func NewStats() SignatureRequestHandlerStats { - return &handlerStats{ - signatureRequest: metrics.GetOrRegisterCounter("signature_request_count", nil), - signatureHit: metrics.GetOrRegisterCounter("signature_request_hit", nil), - signatureMiss: metrics.GetOrRegisterCounter("signature_request_miss", nil), - signatureProcessingTime: metrics.GetOrRegisterTimer("signature_request_duration", nil), - } -} - -func (h *handlerStats) IncSignatureRequest() { h.signatureRequest.Inc(1) } -func (h *handlerStats) IncSignatureHit() { h.signatureHit.Inc(1) } -func (h *handlerStats) IncSignatureMiss() { h.signatureMiss.Inc(1) } -func (h *handlerStats) UpdateSignatureRequestTime(duration time.Duration) { - h.signatureProcessingTime.Update(duration) -} - -// MockSignatureRequestHandlerStats is mock for capturing and asserting on handler metrics in test -type MockSignatureRequestHandlerStats struct { - lock sync.Mutex - - SignatureRequestCount, - SignatureRequestHit, - SignatureRequestMiss uint32 - SignatureRequestDuration time.Duration -} - -func (m *MockSignatureRequestHandlerStats) Reset() { - m.lock.Lock() - defer m.lock.Unlock() - - m.SignatureRequestCount = 0 - m.SignatureRequestHit = 0 - m.SignatureRequestMiss = 0 - m.SignatureRequestDuration = 0 -} - -func (m *MockSignatureRequestHandlerStats) IncSignatureRequest() { - m.lock.Lock() - defer m.lock.Unlock() - m.SignatureRequestCount++ -} - -func (m *MockSignatureRequestHandlerStats) IncSignatureHit() { - m.lock.Lock() - defer m.lock.Unlock() - m.SignatureRequestHit++ -} - -func (m *MockSignatureRequestHandlerStats) IncSignatureMiss() { - m.lock.Lock() - defer m.lock.Unlock() - m.SignatureRequestMiss++ -} - -func (m *MockSignatureRequestHandlerStats) UpdateSignatureRequestTime(duration time.Duration) { - m.lock.Lock() - defer m.lock.Unlock() - m.SignatureRequestDuration += duration -} From 07903bc5e02de19c77faa5809ae928435b495f47 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Wed, 11 Oct 2023 14:42:32 -0400 Subject: [PATCH 38/52] predicate: document predicate results size cap (#943) * predicate: document predicate results size cap * Update predicate/Results.md Co-authored-by: Darioush Jalali Signed-off-by: aaronbuchwald --------- Signed-off-by: aaronbuchwald Co-authored-by: Darioush Jalali Co-authored-by: Ceyhun Onur --- predicate/{README.md => Predicate.md} | 0 precompile/results/README.md => predicate/Results.md | 12 ++++++++++++ 2 files changed, 12 insertions(+) rename predicate/{README.md => Predicate.md} (100%) rename precompile/results/README.md => predicate/Results.md (76%) diff --git a/predicate/README.md b/predicate/Predicate.md similarity index 100% rename from predicate/README.md rename to predicate/Predicate.md diff --git a/precompile/results/README.md b/predicate/Results.md similarity index 76% rename from precompile/results/README.md rename to predicate/Results.md index c5ccb97431..67e6465030 100644 --- a/precompile/results/README.md +++ b/predicate/Results.md @@ -112,3 +112,15 @@ TxPredicateResults // bytes 0x01, 0x02, 0x03 ``` + +### Maximum Size + +Results has a maximum size of 1MB enforced by the codec. The actual size depends on how much data the Precompile predicates may put into the results, the gas cost they charge, and the block gas limit. + +The Results maximum size should comfortably exceed the maximum value that could happen in practice, so that a correct block builder will not attempt to build a block and fail to marshal the predicate results using the codec. + +We make this easy to reason about by assigning a minimum gas cost to the `PredicateGas` function of precompiles. In the case of Warp, the minimum gas cost is set to 200k gas, which can lead to at most 32 additional bytes being included in Results. + +The additional bytes come from the transaction hash (32 bytes), length of tx predicate results (4 bytes), the precompile address (20 bytes), length of the bytes result (4 bytes), and the additional byte in the results bitset (1 byte). This results in 200k gas contributing a maximum of 61 additional bytes to Result. + +For a block with a maximum gas limit of 100M, the block can include up to 500 validated predicates based contributing to the size of Result. At 61 bytes / validated predicate, this yields ~30KB, which is well short of the 1MB cap. From d3bc38df6c44e95db29e95d656688d8bf2a86c4e Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 11 Oct 2023 23:56:18 +0300 Subject: [PATCH 39/52] bump avalanchego to v1.10.12 (#952) --- README.md | 8 ++++---- go.mod | 13 +++++++------ go.sum | 26 ++++++++++++++------------ scripts/versions.sh | 2 +- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 0eaad37c0a..a4c5f7dd0e 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,9 @@ The Subnet EVM runs in a separate process from the main AvalancheGo process and [v0.5.1] AvalancheGo@v1.10.1-v1.10.4 (Protocol Version: 26) [v0.5.2] AvalancheGo@v1.10.1-v1.10.4 (Protocol Version: 26) [v0.5.3] AvalancheGo@v1.10.5-v1.10.6 (Protocol Version: 27) -[v0.5.4] AvalancheGo@v1.10.9-v1.10.10 (Protocol Version: 28) -[v0.5.5] AvalancheGo@v1.10.9-v1.10.10 (Protocol Version: 28) -[v0.5.6] AvalancheGo@v1.10.9-v1.10.10 (Protocol Version: 28) +[v0.5.4] AvalancheGo@v1.10.9-v1.10.12 (Protocol Version: 28) +[v0.5.5] AvalancheGo@v1.10.9-v1.10.12 (Protocol Version: 28) +[v0.5.6] AvalancheGo@v1.10.9-v1.10.12 (Protocol Version: 28) ``` ## API @@ -63,7 +63,7 @@ The Subnet EVM supports the following API namespaces: - `debug` Only the `eth` namespace is enabled by default. -Subnet EVM is a simplified version of [Coreth VM (C-Chain)](https://github.com/ava-labs/coreth). +Subnet EVM is a simplified version of [Coreth VM (C-Chain)](https://github.com/ava-labs/coreth). Full documentation for the C-Chain's API can be found [here](https://docs.avax.network/apis/avalanchego/apis/c-chain). ## Compatibility diff --git a/go.mod b/go.mod index 1bf813f01b..8fa6860b64 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/VictoriaMetrics/fastcache v1.10.0 github.com/ava-labs/avalanche-network-runner v1.7.2 - github.com/ava-labs/avalanchego v1.10.10 + github.com/ava-labs/avalanchego v1.10.12 github.com/cespare/cp v0.1.0 github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 github.com/davecgh/go-spew v1.1.1 @@ -43,10 +43,10 @@ require ( github.com/tyler-smith/go-bip39 v1.1.0 github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa go.uber.org/mock v0.2.0 - golang.org/x/crypto v0.1.0 + golang.org/x/crypto v0.14.0 golang.org/x/sync v0.2.0 - golang.org/x/sys v0.8.0 - golang.org/x/text v0.8.0 + golang.org/x/sys v0.13.0 + golang.org/x/text v0.13.0 golang.org/x/time v0.1.0 google.golang.org/protobuf v1.30.0 ) @@ -87,6 +87,7 @@ require ( github.com/google/btree v1.1.2 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect + github.com/google/renameio/v2 v2.0.0 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/graph-gophers/graphql-go v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect @@ -144,8 +145,8 @@ require ( go.uber.org/zap v1.24.0 // indirect golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect golang.org/x/mod v0.10.0 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/term v0.7.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/term v0.13.0 // indirect gonum.org/v1/gonum v0.11.0 // indirect google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect google.golang.org/grpc v1.56.0-dev // indirect diff --git a/go.sum b/go.sum index a98b5164c6..a58f39c055 100644 --- a/go.sum +++ b/go.sum @@ -61,8 +61,8 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/ava-labs/avalanche-network-runner v1.7.2 h1:XFad/wZfYzDnqbLzPAPPRYU3a1Zc8QT8x5dtLTS3lUo= github.com/ava-labs/avalanche-network-runner v1.7.2/go.mod h1:naLveusSrP7YgTAqRykD1SyLOAUilCp9jGjk3MDxoPI= -github.com/ava-labs/avalanchego v1.10.10 h1:EYX4LVotcfdtIQ0nJSBTcoisubx/Bzk2tM1aP3yiYiw= -github.com/ava-labs/avalanchego v1.10.10/go.mod h1:6UA0nxxTvvpyuCbP2DSzx9+7uWQfQx9DPApK8JptLiE= +github.com/ava-labs/avalanchego v1.10.12 h1:GmS2/4ugkpxV1sHq4EB+y/wsjhxCZxVgvWAlDRk6TSs= +github.com/ava-labs/avalanchego v1.10.12/go.mod h1:9fKHRV5IrmS+Y8hUEIzDPUEHPIuFm8olPPf40qE46ZQ= github.com/ava-labs/coreth v0.12.5-rc.6 h1:OajGUyKkO5Q82XSuMa8T5UD6QywtCHUiZ4Tv3RFmRBU= github.com/ava-labs/coreth v0.12.5-rc.6/go.mod h1:s5wVyy+5UCCk2m0Tq3jVmy0UqOpKBDYqRE13gInCJVs= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= @@ -322,6 +322,8 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U= github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= +github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= @@ -677,8 +679,8 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -764,8 +766,8 @@ golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -857,13 +859,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -874,8 +876,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/scripts/versions.sh b/scripts/versions.sh index 4506d89c6a..31b61a6fe5 100644 --- a/scripts/versions.sh +++ b/scripts/versions.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Don't export them as they're used in the context of other calls -AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.10.10'} +AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.10.12'} AVALANCHEGO_VERSION=${AVALANCHEGO_VERSION:-$AVALANCHE_VERSION} GINKGO_VERSION=${GINKGO_VERSION:-'v2.2.0'} From 4c873c191b495c18ee4cc143e411f490ac06add1 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 11 Oct 2023 23:57:30 +0300 Subject: [PATCH 40/52] add hardhat tests for warp (#935) * fix warp event packing * add hardhat tests for warp * remove destination fields from test * parse from event data * move setup to beforesuite * Revert "move setup to beforesuite" This reverts commit 59abbf14432d29a6c3a82cfcb3e18badadfcd87b. * cleanups * move variables to definition * remove hex checks * use mocha conf to increase timeouts --- contracts/hardhat.config.ts | 3 +++ contracts/test/fee_manager.ts | 2 -- contracts/test/reward_manager.ts | 4 +--- contracts/test/warp.ts | 38 +++++++++++++++++++++++++++++ tests/utils/command.go | 5 +++- tests/warp/warp_test.go | 41 ++++++++++++++++++++++++++++++++ warp/payload/README.md | 6 +++-- x/warp/README.md | 4 +--- 8 files changed, 92 insertions(+), 11 deletions(-) create mode 100644 contracts/test/warp.ts diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index 806fd755be..a132a2c89a 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -47,5 +47,8 @@ export default { ], pollingInterval: "1s" }, + }, + mocha: { + timeout: 30000 } } diff --git a/contracts/test/fee_manager.ts b/contracts/test/fee_manager.ts index ce75b56cfd..a5b47c5598 100644 --- a/contracts/test/fee_manager.ts +++ b/contracts/test/fee_manager.ts @@ -11,8 +11,6 @@ const FEE_MANAGER = "0x0200000000000000000000000000000000000003" const GENESIS_CONFIG = require('../../tests/precompile/genesis/fee_manager.json') describe("ExampleFeeManager", function () { - this.timeout("30s") - beforeEach("setup DS-Test contract", async function () { const signer = await ethers.getSigner(ADMIN_ADDRESS) const feeManagerPromise = ethers.getContractAt("IFeeManager", FEE_MANAGER, signer) diff --git a/contracts/test/reward_manager.ts b/contracts/test/reward_manager.ts index b09b65e831..7f0f940419 100644 --- a/contracts/test/reward_manager.ts +++ b/contracts/test/reward_manager.ts @@ -9,8 +9,6 @@ const ADMIN_ADDRESS = "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" const REWARD_MANAGER_ADDRESS = "0x0200000000000000000000000000000000000004" describe("ExampleRewardManager", function () { - this.timeout("30s") - beforeEach('Setup DS-Test contract', async function () { const signer = await ethers.getSigner(ADMIN_ADDRESS) const rewardManagerPromise = ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDRESS, signer) @@ -35,7 +33,7 @@ describe("ExampleRewardManager", function () { test("should be appointed as reward address", "step_setRewardAddress") - // we need to change the fee receiver, send a transaction for the new receiver to receive fees, then check the balance change. + // we need to change the fee receiver, send a transaction for the new receiver to receive fees, then check the balance change. // the new fee receiver won't receive fees in the same block where it was set. test("should be able to receive fees", ["step_setupReceiveFees", "step_receiveFees", "step_checkReceiveFees"]) diff --git a/contracts/test/warp.ts b/contracts/test/warp.ts new file mode 100644 index 0000000000..a6b847a829 --- /dev/null +++ b/contracts/test/warp.ts @@ -0,0 +1,38 @@ +// (c) 2019-2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { expect } from "chai"; +import { + Contract, +} from "ethers" +import { ethers } from "hardhat" + +const WARP_ADDRESS = "0x0200000000000000000000000000000000000005"; +let senderAddress = process.env["SENDER_ADDRESS"]; +// Expected to be a hex string +let payload = process.env["PAYLOAD"]; +let expectedUnsignedMessage = process.env["EXPECTED_UNSIGNED_MESSAGE"]; +let sourceID = process.env["SOURCE_CHAIN_ID"]; + +describe("IWarpMessenger", function () { + let owner: SignerWithAddress + let contract: Contract + before(async function () { + owner = await ethers.getSigner(senderAddress); + contract = await ethers.getContractAt("IWarpMessenger", WARP_ADDRESS, owner) + }); + + it("contract should be to send warp message", async function () { + console.log(`Sending warp message with payload ${payload}, expected unsigned message ${expectedUnsignedMessage}`); + + await expect(contract.sendWarpMessage(payload)) + .to.emit(contract, 'SendWarpMessage') + .withArgs(senderAddress, expectedUnsignedMessage); + }) + + it("should be able to fetch correct blockchain ID", async function () { + let blockchainID = await contract.getBlockchainID(); + expect(blockchainID).to.be.equal(sourceID); + }) +}) diff --git a/tests/utils/command.go b/tests/utils/command.go index 8a1a9cffb3..bd5a982e9b 100644 --- a/tests/utils/command.go +++ b/tests/utils/command.go @@ -95,9 +95,12 @@ func RegisterNodeRun() { // [execPath] is the path where the test command is executed func RunHardhatTests(ctx context.Context, blockchainID string, execPath string, testPath string) { chainURI := GetDefaultChainURI(blockchainID) + RunHardhatTestsCustomURI(ctx, chainURI, execPath, testPath) +} + +func RunHardhatTestsCustomURI(ctx context.Context, chainURI string, execPath string, testPath string) { log.Info( "Executing HardHat tests on blockchain", - "blockchainID", blockchainID, "testPath", testPath, "ChainURI", chainURI, ) diff --git a/tests/warp/warp_test.go b/tests/warp/warp_test.go index 9e46670f8b..962995a00f 100644 --- a/tests/warp/warp_test.go +++ b/tests/warp/warp_test.go @@ -7,11 +7,13 @@ package warp import ( "context" "crypto/ecdsa" + "encoding/hex" "fmt" "math/big" "os" "strings" "testing" + "time" "github.com/ava-labs/avalanche-network-runner/rpcpb" "github.com/ava-labs/avalanchego/api/info" @@ -28,6 +30,7 @@ import ( "github.com/ava-labs/subnet-evm/tests/utils" "github.com/ava-labs/subnet-evm/tests/utils/runner" warpBackend "github.com/ava-labs/subnet-evm/warp" + warpPayload "github.com/ava-labs/subnet-evm/warp/payload" "github.com/ava-labs/subnet-evm/x/warp" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -362,4 +365,42 @@ var _ = ginkgo.Describe("[Warp]", ginkgo.Ordered, func() { gomega.Expect(err).Should(gomega.BeNil()) gomega.Expect(receipt.Status).Should(gomega.Equal(types.ReceiptStatusSuccessful)) }) + + ginkgo.It("Send Message from A to B from Hardhat", ginkgo.Label("Warp", "IWarpMessenger", "SendWarpMessage"), func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + log.Info("Subscribing to new heads") + newHeads := make(chan *types.Header, 10) + sub, err := chainAWSClient.SubscribeNewHead(ctx, newHeads) + gomega.Expect(err).Should(gomega.BeNil()) + defer sub.Unsubscribe() + + rpcURI := toRPCURI(chainAURIs[0], blockchainIDA.String()) + senderAddress := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") + addressedPayload, err := warpPayload.NewAddressedPayload( + senderAddress, + payload, + ) + gomega.Expect(err).Should(gomega.BeNil()) + expectedUnsignedMessage, err := avalancheWarp.NewUnsignedMessage( + 1337, + blockchainIDA, + addressedPayload.Bytes(), + ) + gomega.Expect(err).Should(gomega.BeNil()) + + os.Setenv("SENDER_ADDRESS", senderAddress.Hex()) + os.Setenv("SOURCE_CHAIN_ID", "0x"+blockchainIDA.Hex()) + os.Setenv("PAYLOAD", "0x"+common.Bytes2Hex(payload)) + os.Setenv("EXPECTED_UNSIGNED_MESSAGE", "0x"+hex.EncodeToString(expectedUnsignedMessage.Bytes())) + + cmdPath := "./contracts" + // test path is relative to the cmd path + testPath := "./test/warp.ts" + utils.RunHardhatTestsCustomURI(ctx, rpcURI, cmdPath, testPath) + }) }) + +func toRPCURI(uri string, blockchainID string) string { + return fmt.Sprintf("%s/ext/bc/%s/rpc", uri, blockchainID) +} diff --git a/warp/payload/README.md b/warp/payload/README.md index 1da3b92712..3db2a0e751 100644 --- a/warp/payload/README.md +++ b/warp/payload/README.md @@ -5,7 +5,8 @@ An Avalanche Unsigned Warp Message already includes a `networkID`, `sourceChainI ## AddressedPayload AddressedPayload: -``` + +```text +---------------------+----------+-------------------+ | codecID : uint16 | 2 bytes | +---------------------+----------+-------------------+ @@ -27,7 +28,8 @@ AddressedPayload: ## BlockHashPayload BlockHashPayload: -``` + +```text +-----------------+----------+-----------+ | codecID : uint16 | 2 bytes | +-----------------+----------+-----------+ diff --git a/x/warp/README.md b/x/warp/README.md index 846a258650..b7aa5fd4a2 100644 --- a/x/warp/README.md +++ b/x/warp/README.md @@ -52,7 +52,6 @@ Additionally, the `SourceChainID` is excluded because anyone parsing the chain c The actual `message` is the entire [Avalanche Warp Unsigned Message](https://github.com/ava-labs/avalanchego/blob/master/vms/platformvm/warp/unsigned_message.go#L14) including the Subnet-EVM [Addressed Payload](../../../warp/payload/payload.go). - #### getVerifiedMessage `getVerifiedMessage` is used to read the contents of the delivered Avalanche Warp Message into the expected format. @@ -72,11 +71,10 @@ This pre-verification is performed using the ProposerVM Block header during [blo `getBlockchainID` returns the blockchainID of the blockchain that Subnet-EVM is running on. -This is different from the conventional Ethereum ChainID registered to https://chainlist.org/. +This is different from the conventional Ethereum ChainID registered to [ChainList](https://chainlist.org/). The `blockchainID` in Avalanche refers to the txID that created the blockchain on the Avalanche P-Chain ([docs](https://docs.avax.network/specs/platform-transaction-serialization#unsigned-create-chain-tx)). - ### Predicate Encoding Avalanche Warp Messages are encoded as a signed Avalanche [Warp Message](https://github.com/ava-labs/avalanchego/blob/v1.10.4/vms/platformvm/warp/message.go#L7) where the [UnsignedMessage](https://github.com/ava-labs/avalanchego/blob/v1.10.4/vms/platformvm/warp/unsigned_message.go#L14)'s payload includes an [AddressedPayload](../../../warp/payload/payload.go). From 326c6601664811f20a7e97d02509bce1b0e49c43 Mon Sep 17 00:00:00 2001 From: Cesar <137245636+nytzuga@users.noreply.github.com> Date: Thu, 12 Oct 2023 16:45:10 -0300 Subject: [PATCH 41/52] Move warp payload to avalanchego (#923) * Integrating Warp Payload Types into AvalancheGo for Cross-VM Compatibility This fixes https://github.com/ava-labs/avalanchego/discussions/2050 and https://github.com/ava-labs/avalanchego/pull/2116 * Update types * Do not update go.sum * Fixed more type changes from the migration * Drop the alias `avalancheWarpPayload` The alias is not longer needed --------- Co-authored-by: Aaron Buchwald --- plugin/evm/block.go | 4 +- plugin/evm/vm_warp_test.go | 38 +++++------ tests/warp/warp_test.go | 14 ++-- warp/payload/addressed_payload.go | 60 ----------------- warp/payload/block_hash_payload.go | 57 ---------------- warp/payload/codec.go | 43 ------------- warp/payload/payload_test.go | 100 ----------------------------- x/warp/contract.go | 14 ++-- x/warp/contract_test.go | 28 ++++---- x/warp/contract_warp_handler.go | 10 +-- x/warp/predicate_test.go | 9 +-- 11 files changed, 59 insertions(+), 318 deletions(-) delete mode 100644 warp/payload/addressed_payload.go delete mode 100644 warp/payload/block_hash_payload.go delete mode 100644 warp/payload/codec.go delete mode 100644 warp/payload/payload_test.go diff --git a/plugin/evm/block.go b/plugin/evm/block.go index 9c84622cf2..a600292849 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -13,13 +13,13 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ava-labs/subnet-evm/predicate" - "github.com/ava-labs/subnet-evm/warp/payload" "github.com/ava-labs/subnet-evm/x/warp" "github.com/ava-labs/avalanchego/ids" @@ -132,7 +132,7 @@ func (b *Block) handlePrecompileAccept(rules *params.Rules, sharedMemoryWriter * // If Warp is enabled, add the block hash as an unsigned message to the warp backend. if rules.IsPrecompileEnabled(warp.ContractAddress) { - blockHashPayload, err := payload.NewBlockHashPayload(b.ethBlock.Hash()) + blockHashPayload, err := payload.NewHash(ids.ID(b.ethBlock.Hash().Bytes())) if err != nil { return fmt.Errorf("failed to create block hash payload: %w", err) } diff --git a/plugin/evm/vm_warp_test.go b/plugin/evm/vm_warp_test.go index 0f6934f357..ff98ed2405 100644 --- a/plugin/evm/vm_warp_test.go +++ b/plugin/evm/vm_warp_test.go @@ -21,6 +21,7 @@ import ( "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/components/chain" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/types" @@ -29,7 +30,6 @@ import ( "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/predicate" subnetEVMUtils "github.com/ava-labs/subnet-evm/utils" - warpPayload "github.com/ava-labs/subnet-evm/warp/payload" "github.com/ava-labs/subnet-evm/x/warp" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -62,9 +62,9 @@ func TestSendWarpMessage(t *testing.T) { logsSub := vm.eth.APIBackend.SubscribeAcceptedLogsEvent(acceptedLogsChan) defer logsSub.Unsubscribe() - payload := utils.RandomBytes(100) + payloadData := utils.RandomBytes(100) - warpSendMessageInput, err := warp.PackSendWarpMessage(payload) + warpSendMessageInput, err := warp.PackSendWarpMessage(payloadData) require.NoError(err) // Submit a transaction to trigger sending a warp message @@ -128,10 +128,10 @@ func TestValidateWarpMessage(t *testing.T) { require := require.New(t) sourceChainID := ids.GenerateTestID() sourceAddress := common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2") - payload := []byte{1, 2, 3} - addressedPayload, err := warpPayload.NewAddressedPayload( - sourceAddress, - payload, + payloadData := []byte{1, 2, 3} + addressedPayload, err := payload.NewAddressedCall( + sourceAddress.Bytes(), + payloadData, ) require.NoError(err) unsignedMessage, err := avalancheWarp.NewUnsignedMessage(testNetworkID, sourceChainID, addressedPayload.Bytes()) @@ -143,7 +143,7 @@ func TestValidateWarpMessage(t *testing.T) { uint32(0), sourceChainID, sourceAddress, - payload, + payloadData, ) require.NoError(err) @@ -154,10 +154,10 @@ func TestValidateInvalidWarpMessage(t *testing.T) { require := require.New(t) sourceChainID := ids.GenerateTestID() sourceAddress := common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2") - payload := []byte{1, 2, 3} - addressedPayload, err := warpPayload.NewAddressedPayload( - sourceAddress, - payload, + payloadData := []byte{1, 2, 3} + addressedPayload, err := payload.NewAddressedCall( + sourceAddress.Bytes(), + payloadData, ) require.NoError(err) unsignedMessage, err := avalancheWarp.NewUnsignedMessage(testNetworkID, sourceChainID, addressedPayload.Bytes()) @@ -177,7 +177,7 @@ func TestValidateWarpBlockHash(t *testing.T) { require := require.New(t) sourceChainID := ids.GenerateTestID() blockHash := ids.GenerateTestID() - blockHashPayload, err := warpPayload.NewBlockHashPayload(common.Hash(blockHash)) + blockHashPayload, err := payload.NewHash(blockHash) require.NoError(err) unsignedMessage, err := avalancheWarp.NewUnsignedMessage(testNetworkID, sourceChainID, blockHashPayload.Bytes()) require.NoError(err) @@ -198,7 +198,7 @@ func TestValidateInvalidWarpBlockHash(t *testing.T) { require := require.New(t) sourceChainID := ids.GenerateTestID() blockHash := ids.GenerateTestID() - blockHashPayload, err := warpPayload.NewBlockHashPayload(common.Hash(blockHash)) + blockHashPayload, err := payload.NewHash(blockHash) require.NoError(err) unsignedMessage, err := avalancheWarp.NewUnsignedMessage(testNetworkID, sourceChainID, blockHashPayload.Bytes()) require.NoError(err) @@ -389,11 +389,11 @@ func TestReceiveWarpMessage(t *testing.T) { logsSub := vm.eth.APIBackend.SubscribeAcceptedLogsEvent(acceptedLogsChan) defer logsSub.Unsubscribe() - payload := utils.RandomBytes(100) + payloadData := utils.RandomBytes(100) - addressedPayload, err := warpPayload.NewAddressedPayload( - testEthAddrs[0], - payload, + addressedPayload, err := payload.NewAddressedCall( + testEthAddrs[0].Bytes(), + payloadData, ) require.NoError(err) unsignedMessage, err := avalancheWarp.NewUnsignedMessage( @@ -531,7 +531,7 @@ func TestReceiveWarpMessage(t *testing.T) { Message: warp.WarpMessage{ SourceChainID: common.Hash(vm.ctx.ChainID), OriginSenderAddress: testEthAddrs[0], - Payload: payload, + Payload: payloadData, }, Valid: true, }) diff --git a/tests/warp/warp_test.go b/tests/warp/warp_test.go index 962995a00f..2311bcd974 100644 --- a/tests/warp/warp_test.go +++ b/tests/warp/warp_test.go @@ -21,6 +21,7 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/set" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/ethclient" "github.com/ava-labs/subnet-evm/interfaces" @@ -30,7 +31,6 @@ import ( "github.com/ava-labs/subnet-evm/tests/utils" "github.com/ava-labs/subnet-evm/tests/utils/runner" warpBackend "github.com/ava-labs/subnet-evm/warp" - warpPayload "github.com/ava-labs/subnet-evm/warp/payload" "github.com/ava-labs/subnet-evm/x/warp" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -54,7 +54,7 @@ var ( chainID = big.NewInt(99999) fundedKey *ecdsa.PrivateKey fundedAddress common.Address - payload = []byte{1, 2, 3} + testPayload = []byte{1, 2, 3} txSigner = types.LatestSignerForChainID(chainID) ) @@ -188,7 +188,7 @@ var _ = ginkgo.Describe("[Warp]", ginkgo.Ordered, func() { startingNonce, err := chainAWSClient.NonceAt(ctx, fundedAddress, nil) gomega.Expect(err).Should(gomega.BeNil()) - packedInput, err := warp.PackSendWarpMessage(payload) + packedInput, err := warp.PackSendWarpMessage(testPayload) gomega.Expect(err).Should(gomega.BeNil()) tx := types.NewTx(&types.DynamicFeeTx{ ChainID: chainID, @@ -377,9 +377,9 @@ var _ = ginkgo.Describe("[Warp]", ginkgo.Ordered, func() { rpcURI := toRPCURI(chainAURIs[0], blockchainIDA.String()) senderAddress := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") - addressedPayload, err := warpPayload.NewAddressedPayload( - senderAddress, - payload, + addressedPayload, err := payload.NewAddressedCall( + senderAddress.Bytes(), + testPayload, ) gomega.Expect(err).Should(gomega.BeNil()) expectedUnsignedMessage, err := avalancheWarp.NewUnsignedMessage( @@ -391,7 +391,7 @@ var _ = ginkgo.Describe("[Warp]", ginkgo.Ordered, func() { os.Setenv("SENDER_ADDRESS", senderAddress.Hex()) os.Setenv("SOURCE_CHAIN_ID", "0x"+blockchainIDA.Hex()) - os.Setenv("PAYLOAD", "0x"+common.Bytes2Hex(payload)) + os.Setenv("PAYLOAD", "0x"+common.Bytes2Hex(testPayload)) os.Setenv("EXPECTED_UNSIGNED_MESSAGE", "0x"+hex.EncodeToString(expectedUnsignedMessage.Bytes())) cmdPath := "./contracts" diff --git a/warp/payload/addressed_payload.go b/warp/payload/addressed_payload.go deleted file mode 100644 index a0ed6767d6..0000000000 --- a/warp/payload/addressed_payload.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package payload - -import ( - "fmt" - - "github.com/ethereum/go-ethereum/common" -) - -// AddressedPayload defines the format for delivering a point to point message across VMs -// ie. (ChainA, AddressA) -> (ChainB, AddressB) -type AddressedPayload struct { - SourceAddress common.Address `serialize:"true"` - Payload []byte `serialize:"true"` - - bytes []byte -} - -// NewAddressedPayload creates a new *AddressedPayload and initializes it. -func NewAddressedPayload(sourceAddress common.Address, payload []byte) (*AddressedPayload, error) { - ap := &AddressedPayload{ - SourceAddress: sourceAddress, - Payload: payload, - } - return ap, ap.initialize() -} - -// ParseAddressedPayload converts a slice of bytes into an initialized -// AddressedPayload. -func ParseAddressedPayload(b []byte) (*AddressedPayload, error) { - var unmarshalledPayloadIntf any - if _, err := c.Unmarshal(b, &unmarshalledPayloadIntf); err != nil { - return nil, err - } - payload, ok := unmarshalledPayloadIntf.(*AddressedPayload) - if !ok { - return nil, fmt.Errorf("%w: %T", errWrongType, unmarshalledPayloadIntf) - } - payload.bytes = b - return payload, nil -} - -// initialize recalculates the result of Bytes(). -func (a *AddressedPayload) initialize() error { - payloadIntf := any(a) - bytes, err := c.Marshal(codecVersion, &payloadIntf) - if err != nil { - return fmt.Errorf("couldn't marshal warp addressed payload: %w", err) - } - a.bytes = bytes - return nil -} - -// Bytes returns the binary representation of this payload. It assumes that the -// payload is initialized from either NewAddressedPayload or ParseAddressedPayload. -func (a *AddressedPayload) Bytes() []byte { - return a.bytes -} diff --git a/warp/payload/block_hash_payload.go b/warp/payload/block_hash_payload.go deleted file mode 100644 index bd3ce6eda2..0000000000 --- a/warp/payload/block_hash_payload.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package payload - -import ( - "fmt" - - "github.com/ethereum/go-ethereum/common" -) - -// BlockHashPayload includes the block hash -type BlockHashPayload struct { - BlockHash common.Hash `serialize:"true"` - - bytes []byte -} - -// NewBlockHashPayload creates a new *BlockHashPayload and initializes it. -func NewBlockHashPayload(blockHash common.Hash) (*BlockHashPayload, error) { - bhp := &BlockHashPayload{ - BlockHash: blockHash, - } - return bhp, bhp.initialize() -} - -// ParseBlockHashPayload converts a slice of bytes into an initialized -// BlockHashPayload -func ParseBlockHashPayload(b []byte) (*BlockHashPayload, error) { - var unmarshalledPayloadIntf any - if _, err := c.Unmarshal(b, &unmarshalledPayloadIntf); err != nil { - return nil, err - } - payload, ok := unmarshalledPayloadIntf.(*BlockHashPayload) - if !ok { - return nil, fmt.Errorf("%w: %T", errWrongType, unmarshalledPayloadIntf) - } - payload.bytes = b - return payload, nil -} - -// initialize recalculates the result of Bytes(). -func (b *BlockHashPayload) initialize() error { - payloadIntf := any(b) - bytes, err := c.Marshal(codecVersion, &payloadIntf) - if err != nil { - return fmt.Errorf("couldn't marshal block hash payload: %w", err) - } - b.bytes = bytes - return nil -} - -// Bytes returns the binary representation of this payload. It assumes that the -// payload is initialized from either NewBlockHashPayload or ParseBlockHashPayload. -func (b *BlockHashPayload) Bytes() []byte { - return b.bytes -} diff --git a/warp/payload/codec.go b/warp/payload/codec.go deleted file mode 100644 index 459506bf4d..0000000000 --- a/warp/payload/codec.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package payload - -import ( - "errors" - - "github.com/ava-labs/avalanchego/codec" - "github.com/ava-labs/avalanchego/codec/linearcodec" - "github.com/ava-labs/avalanchego/utils/units" - "github.com/ava-labs/avalanchego/utils/wrappers" -) - -var errWrongType = errors.New("wrong payload type") - -const ( - codecVersion = 0 - - MaxMessageSize = 24 * units.KiB - - // Note: Modifying this variable can have subtle implications on memory - // usage when parsing malformed payloads. - MaxSliceLen = 24 * units.KiB -) - -// Codec does serialization and deserialization for Warp messages. -var c codec.Manager - -func init() { - c = codec.NewManager(MaxMessageSize) - lc := linearcodec.NewCustomMaxLength(MaxSliceLen) - - errs := wrappers.Errs{} - errs.Add( - lc.RegisterType(&AddressedPayload{}), - lc.RegisterType(&BlockHashPayload{}), - c.RegisterCodec(codecVersion, lc), - ) - if errs.Errored() { - panic(errs.Err) - } -} diff --git a/warp/payload/payload_test.go b/warp/payload/payload_test.go deleted file mode 100644 index 69856538cb..0000000000 --- a/warp/payload/payload_test.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package payload - -import ( - "encoding/base64" - "testing" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" -) - -func TestAddressedPayload(t *testing.T) { - require := require.New(t) - - addressedPayload, err := NewAddressedPayload( - common.Address(ids.GenerateTestShortID()), - []byte{1, 2, 3}, - ) - require.NoError(err) - - addressedPayloadBytes := addressedPayload.Bytes() - addressedPayload2, err := ParseAddressedPayload(addressedPayloadBytes) - require.NoError(err) - require.Equal(addressedPayload, addressedPayload2) -} - -func TestParseAddressedPayloadJunk(t *testing.T) { - _, err := ParseAddressedPayload(utils.RandomBytes(1024)) - require.Error(t, err) -} - -func TestParseAddressedPayload(t *testing.T) { - base64Payload := "AAAAAAAAAQIDAAAAAAAAAAAAAAAAAAAAAAAAAAADCgsM" - payload := &AddressedPayload{ - SourceAddress: common.Address{1, 2, 3}, - Payload: []byte{10, 11, 12}, - } - - require.NoError(t, payload.initialize()) - - require.Equal(t, base64Payload, base64.StdEncoding.EncodeToString(payload.Bytes())) - - parsedPayload, err := ParseAddressedPayload(payload.Bytes()) - require.NoError(t, err) - require.Equal(t, payload, parsedPayload) -} - -func TestBlockHashPayload(t *testing.T) { - require := require.New(t) - - blockHashPayload, err := NewBlockHashPayload(common.Hash(ids.GenerateTestID())) - require.NoError(err) - - blockHashPayloadBytes := blockHashPayload.Bytes() - blockHashPayload2, err := ParseBlockHashPayload(blockHashPayloadBytes) - require.NoError(err) - require.Equal(blockHashPayload, blockHashPayload2) -} - -func TestParseBlockHashPayloadJunk(t *testing.T) { - _, err := ParseBlockHashPayload(utils.RandomBytes(1024)) - require.Error(t, err) -} - -func TestParseBlockHashPayload(t *testing.T) { - base64Payload := "AAAAAAABBAUGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" - payload := &BlockHashPayload{ - BlockHash: common.Hash{4, 5, 6}, - } - - require.NoError(t, payload.initialize()) - - require.Equal(t, base64Payload, base64.StdEncoding.EncodeToString(payload.Bytes())) - - parsedPayload, err := ParseBlockHashPayload(payload.Bytes()) - require.NoError(t, err) - require.Equal(t, payload, parsedPayload) -} - -func TestParseWrongPayloadType(t *testing.T) { - require := require.New(t) - blockHashPayload, err := NewBlockHashPayload(common.Hash(ids.GenerateTestID())) - require.NoError(err) - - addressedPayload, err := NewAddressedPayload( - common.Address(ids.GenerateTestShortID()), - []byte{1, 2, 3}, - ) - require.NoError(err) - - _, err = ParseAddressedPayload(blockHashPayload.Bytes()) - require.ErrorIs(err, errWrongType) - - _, err = ParseBlockHashPayload(addressedPayload.Bytes()) - require.ErrorIs(err, errWrongType) -} diff --git a/x/warp/contract.go b/x/warp/contract.go index f25e59cb54..2486ea2ddb 100644 --- a/x/warp/contract.go +++ b/x/warp/contract.go @@ -8,11 +8,11 @@ import ( "fmt" "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/subnet-evm/accounts/abi" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/vmerrs" - warpPayload "github.com/ava-labs/subnet-evm/warp/payload" _ "embed" @@ -200,8 +200,8 @@ func UnpackSendWarpMessageInput(input []byte) ([]byte, error) { } // PackSendWarpMessage packs [inputStruct] of type []byte into the appropriate arguments for sendWarpMessage. -func PackSendWarpMessage(payload []byte) ([]byte, error) { - return WarpABI.Pack("sendWarpMessage", payload) +func PackSendWarpMessage(payloadData []byte) ([]byte, error) { + return WarpABI.Pack("sendWarpMessage", payloadData) } // sendWarpMessage constructs an Avalanche Warp Message containing an AddressedPayload and emits a log to signal validators that they should @@ -223,7 +223,7 @@ func sendWarpMessage(accessibleState contract.AccessibleState, caller common.Add return nil, remainingGas, vmerrs.ErrWriteProtection } // unpack the arguments - payload, err := UnpackSendWarpMessageInput(input) + payloadData, err := UnpackSendWarpMessageInput(input) if err != nil { return nil, remainingGas, fmt.Errorf("%w: %s", errInvalidSendInput, err) } @@ -233,9 +233,9 @@ func sendWarpMessage(accessibleState contract.AccessibleState, caller common.Add sourceAddress = caller ) - addressedPayload, err := warpPayload.NewAddressedPayload( - sourceAddress, - payload, + addressedPayload, err := payload.NewAddressedCall( + sourceAddress.Bytes(), + payloadData, ) if err != nil { return nil, remainingGas, err diff --git a/x/warp/contract_test.go b/x/warp/contract_test.go index 02eeeebbf9..e78ba4f20d 100644 --- a/x/warp/contract_test.go +++ b/x/warp/contract_test.go @@ -14,12 +14,12 @@ import ( "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/platformvm/warp" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/precompile/testutils" "github.com/ava-labs/subnet-evm/predicate" "github.com/ava-labs/subnet-evm/vmerrs" - warpPayload "github.com/ava-labs/subnet-evm/warp/payload" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) @@ -136,10 +136,10 @@ func TestSendWarpMessage(t *testing.T) { unsignedWarpMsg, err := UnpackSendWarpEventDataToMessage(logData) require.NoError(t, err) - addressedPayload, err := warpPayload.ParseAddressedPayload(unsignedWarpMsg.Payload) + addressedPayload, err := payload.ParseAddressedCall(unsignedWarpMsg.Payload) require.NoError(t, err) - require.Equal(t, addressedPayload.SourceAddress, callerAddr) + require.Equal(t, common.BytesToAddress(addressedPayload.SourceAddress), callerAddr) require.Equal(t, unsignedWarpMsg.SourceChainID, blockchainID) require.Equal(t, addressedPayload.Payload, sendWarpMessagePayload) }, @@ -155,8 +155,8 @@ func TestGetVerifiedWarpMessage(t *testing.T) { sourceAddress := common.HexToAddress("0x456789") sourceChainID := ids.GenerateTestID() packagedPayloadBytes := []byte("mcsorley") - addressedPayload, err := warpPayload.NewAddressedPayload( - sourceAddress, + addressedPayload, err := payload.NewAddressedCall( + sourceAddress.Bytes(), packagedPayloadBytes, ) require.NoError(t, err) @@ -438,8 +438,8 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { networkID := uint32(54321) callerAddr := common.HexToAddress("0x0123") sourceChainID := ids.GenerateTestID() - blockHash := common.Hash(ids.GenerateTestID()) - blockHashPayload, err := warpPayload.NewBlockHashPayload(blockHash) + blockHash := ids.GenerateTestID() + blockHashPayload, err := payload.NewHash(blockHash) require.NoError(t, err) unsignedWarpMsg, err := avalancheWarp.NewUnsignedMessage(networkID, sourceChainID, blockHashPayload.Bytes()) require.NoError(t, err) @@ -467,7 +467,7 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { res, err := PackGetVerifiedWarpBlockHashOutput(GetVerifiedWarpBlockHashOutput{ WarpBlockHash: WarpBlockHash{ SourceChainID: common.Hash(sourceChainID), - BlockHash: blockHash, + BlockHash: common.Hash(blockHash), }, Valid: true, }) @@ -519,7 +519,7 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { res, err := PackGetVerifiedWarpBlockHashOutput(GetVerifiedWarpBlockHashOutput{ WarpBlockHash: WarpBlockHash{ SourceChainID: common.Hash(sourceChainID), - BlockHash: blockHash, + BlockHash: common.Hash(blockHash), }, Valid: true, }) @@ -583,7 +583,7 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { res, err := PackGetVerifiedWarpBlockHashOutput(GetVerifiedWarpBlockHashOutput{ WarpBlockHash: WarpBlockHash{ SourceChainID: common.Hash(sourceChainID), - BlockHash: blockHash, + BlockHash: common.Hash(blockHash), }, Valid: true, }) @@ -715,12 +715,12 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) { func TestPackEvents(t *testing.T) { sourceChainID := ids.GenerateTestID() sourceAddress := common.HexToAddress("0x0123") - payload := []byte("mcsorley") + payloadData := []byte("mcsorley") networkID := uint32(54321) - addressedPayload, err := warpPayload.NewAddressedPayload( - sourceAddress, - payload, + addressedPayload, err := payload.NewAddressedCall( + sourceAddress.Bytes(), + payloadData, ) require.NoError(t, err) diff --git a/x/warp/contract_warp_handler.go b/x/warp/contract_warp_handler.go index 02d58aa433..a39256bc1f 100644 --- a/x/warp/contract_warp_handler.go +++ b/x/warp/contract_warp_handler.go @@ -8,10 +8,10 @@ import ( "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/predicate" "github.com/ava-labs/subnet-evm/vmerrs" - warpPayload "github.com/ava-labs/subnet-evm/warp/payload" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" ) @@ -100,14 +100,14 @@ func (addressedPayloadHandler) packFailed() []byte { } func (addressedPayloadHandler) handleMessage(warpMessage *warp.Message) ([]byte, error) { - addressedPayload, err := warpPayload.ParseAddressedPayload(warpMessage.UnsignedMessage.Payload) + addressedPayload, err := payload.ParseAddressedCall(warpMessage.UnsignedMessage.Payload) if err != nil { return nil, fmt.Errorf("%w: %s", errInvalidAddressedPayload, err) } return PackGetVerifiedWarpMessageOutput(GetVerifiedWarpMessageOutput{ Message: WarpMessage{ SourceChainID: common.Hash(warpMessage.SourceChainID), - OriginSenderAddress: addressedPayload.SourceAddress, + OriginSenderAddress: common.BytesToAddress(addressedPayload.SourceAddress), Payload: addressedPayload.Payload, }, Valid: true, @@ -121,14 +121,14 @@ func (blockHashHandler) packFailed() []byte { } func (blockHashHandler) handleMessage(warpMessage *warp.Message) ([]byte, error) { - blockHashPayload, err := warpPayload.ParseBlockHashPayload(warpMessage.UnsignedMessage.Payload) + blockHashPayload, err := payload.ParseHash(warpMessage.UnsignedMessage.Payload) if err != nil { return nil, fmt.Errorf("%w: %s", errInvalidBlockHashPayload, err) } return PackGetVerifiedWarpBlockHashOutput(GetVerifiedWarpBlockHashOutput{ WarpBlockHash: WarpBlockHash{ SourceChainID: common.Hash(warpMessage.SourceChainID), - BlockHash: blockHashPayload.BlockHash, + BlockHash: common.BytesToHash(blockHashPayload.Hash[:]), }, Valid: true, }) diff --git a/x/warp/predicate_test.go b/x/warp/predicate_test.go index a5f5557fe1..79cf478815 100644 --- a/x/warp/predicate_test.go +++ b/x/warp/predicate_test.go @@ -18,12 +18,12 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/set" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ava-labs/subnet-evm/precompile/testutils" "github.com/ava-labs/subnet-evm/predicate" subnetEVMUtils "github.com/ava-labs/subnet-evm/utils" - warpPayload "github.com/ava-labs/subnet-evm/warp/payload" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" @@ -42,7 +42,7 @@ var ( // valid unsigned warp message used throughout testing unsignedMsg *avalancheWarp.UnsignedMessage // valid addressed payload - addressedPayload *warpPayload.AddressedPayload + addressedPayload *payload.AddressedCall addressedPayloadBytes []byte // blsSignatures of [unsignedMsg] from each of [testVdrs] blsSignatures []*bls.Signature @@ -81,8 +81,9 @@ func init() { } var err error - addressedPayload, err = warpPayload.NewAddressedPayload( - common.Address(ids.GenerateTestShortID()), + addr := ids.GenerateTestShortID() + addressedPayload, err = payload.NewAddressedCall( + addr[:], []byte{1, 2, 3}, ) if err != nil { From 74adf7eba51de1b0aabb30ffc8e89aa39354a785 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Fri, 13 Oct 2023 16:41:20 -0400 Subject: [PATCH 42/52] Fix aggregate signatures test race (#954) * fix test race * fix test race * fix test race --- warp/aggregator/aggregator_test.go | 70 +++++++++++++++--------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/warp/aggregator/aggregator_test.go b/warp/aggregator/aggregator_test.go index 5135aa648b..6668eba766 100644 --- a/warp/aggregator/aggregator_test.go +++ b/warp/aggregator/aggregator_test.go @@ -142,7 +142,7 @@ func TestAggregateSignatures(t *testing.T) { ) client := NewMockSignatureGetter(ctrl) - client.EXPECT().GetSignature(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errTest).AnyTimes() + client.EXPECT().GetSignature(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errTest).Times(len(vdrSet)) return New(subnetID, state, client) }, unsignedMsg: unsignedMsg, @@ -162,9 +162,9 @@ func TestAggregateSignatures(t *testing.T) { ) client := NewMockSignatureGetter(ctrl) - client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(sig1, nil) - client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(nil, errTest) - client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(nil, errTest) + client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(sig1, nil).Times(1) + client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(nil, errTest).Times(1) + client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(nil, errTest).Times(1) return New(subnetID, state, client) }, unsignedMsg: unsignedMsg, @@ -184,9 +184,9 @@ func TestAggregateSignatures(t *testing.T) { ) client := NewMockSignatureGetter(ctrl) - client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(sig1, nil) - client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(sig2, nil) - client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(nil, errTest) + client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(sig1, nil).Times(1) + client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(sig2, nil).Times(1) + client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(nil, errTest).Times(1) return New(subnetID, state, client) }, unsignedMsg: unsignedMsg, @@ -206,9 +206,9 @@ func TestAggregateSignatures(t *testing.T) { ) client := NewMockSignatureGetter(ctrl) - client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(sig1, nil) - client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(sig2, nil) - client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(nil, errTest) + client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(sig1, nil).Times(1) + client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(sig2, nil).Times(1) + client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(nil, errTest).MaxTimes(1) return New(subnetID, state, client) }, unsignedMsg: unsignedMsg, @@ -229,9 +229,9 @@ func TestAggregateSignatures(t *testing.T) { ) client := NewMockSignatureGetter(ctrl) - client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(sig1, nil) - client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(sig2, nil) - client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(sig3, nil) + client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(sig1, nil).MaxTimes(1) + client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(sig2, nil).MaxTimes(1) + client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(sig3, nil).MaxTimes(1) return New(subnetID, state, client) }, unsignedMsg: unsignedMsg, @@ -252,9 +252,9 @@ func TestAggregateSignatures(t *testing.T) { ) client := NewMockSignatureGetter(ctrl) - client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(nonVdrSig, nil) - client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(sig2, nil) - client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(sig3, nil) + client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(nonVdrSig, nil).MaxTimes(1) + client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(sig2, nil).Times(1) + client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(sig3, nil).Times(1) return New(subnetID, state, client) }, unsignedMsg: unsignedMsg, @@ -275,9 +275,9 @@ func TestAggregateSignatures(t *testing.T) { ) client := NewMockSignatureGetter(ctrl) - client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(nonVdrSig, nil) - client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(nonVdrSig, nil) - client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(nonVdrSig, nil) + client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(nonVdrSig, nil).Times(1) + client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(nonVdrSig, nil).Times(1) + client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(nonVdrSig, nil).Times(1) return New(subnetID, state, client) }, unsignedMsg: unsignedMsg, @@ -297,9 +297,9 @@ func TestAggregateSignatures(t *testing.T) { ) client := NewMockSignatureGetter(ctrl) - client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(nonVdrSig, nil) - client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(nonVdrSig, nil) - client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(sig3, nil) + client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(nonVdrSig, nil).Times(1) + client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(nonVdrSig, nil).Times(1) + client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(sig3, nil).Times(1) return New(subnetID, state, client) }, unsignedMsg: unsignedMsg, @@ -307,7 +307,7 @@ func TestAggregateSignatures(t *testing.T) { expectedErr: avalancheWarp.ErrInsufficientWeight, }, { - name: "2/3 validators reply with signature; 1 invalid signature; sufficient weight", + name: "1/3 validators reply with signature; 1 invalid signature; 1 error; sufficient weight", contextWithCancelFunc: func() (context.Context, context.CancelFunc) { return context.Background(), nil }, @@ -319,9 +319,9 @@ func TestAggregateSignatures(t *testing.T) { ) client := NewMockSignatureGetter(ctrl) - client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(nonVdrSig, nil) - client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(nil, errTest) - client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(sig3, nil) + client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(nonVdrSig, nil).MaxTimes(1) + client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(nil, errTest).MaxTimes(1) + client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(sig3, nil).Times(1) return New(subnetID, state, client) }, unsignedMsg: unsignedMsg, @@ -353,7 +353,7 @@ func TestAggregateSignatures(t *testing.T) { require.ErrorIs(t, err, context.Canceled) return nil, err }, - ) + ).Times(1) client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).DoAndReturn( func(ctx context.Context, _ ids.NodeID, _ *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { <-ctx.Done() @@ -361,7 +361,7 @@ func TestAggregateSignatures(t *testing.T) { require.ErrorIs(t, err, context.Canceled) return nil, err }, - ) + ).MaxTimes(1) client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).DoAndReturn( func(ctx context.Context, _ ids.NodeID, _ *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { <-ctx.Done() @@ -369,7 +369,7 @@ func TestAggregateSignatures(t *testing.T) { require.ErrorIs(t, err, context.Canceled) return nil, err }, - ) + ).MaxTimes(1) return New(subnetID, state, client) }, unsignedMsg: unsignedMsg, @@ -397,7 +397,7 @@ func TestAggregateSignatures(t *testing.T) { cancel() return sig1, nil }, - ) + ).Times(1) client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).DoAndReturn( func(ctx context.Context, _ ids.NodeID, _ *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { // Should not be able to grab another signature since context was cancelled in another go routine @@ -406,7 +406,7 @@ func TestAggregateSignatures(t *testing.T) { require.ErrorIs(t, err, context.Canceled) return nil, err }, - ) + ).MaxTimes(1) client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).DoAndReturn( func(ctx context.Context, _ ids.NodeID, _ *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { // Should not be able to grab another signature since context was cancelled in another go routine @@ -415,7 +415,7 @@ func TestAggregateSignatures(t *testing.T) { require.ErrorIs(t, err, context.Canceled) return nil, err }, - ) + ).MaxTimes(1) return New(subnetID, state, client) }, unsignedMsg: unsignedMsg, @@ -436,8 +436,8 @@ func TestAggregateSignatures(t *testing.T) { ) client := NewMockSignatureGetter(ctrl) - client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(sig1, nil) - client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(sig2, nil) + client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(sig1, nil).Times(1) + client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(sig2, nil).Times(1) client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).DoAndReturn( // The aggregator will receive sig1 and sig2 which is sufficient weight, // so the remaining outstanding goroutine should be cancelled. @@ -447,7 +447,7 @@ func TestAggregateSignatures(t *testing.T) { require.ErrorIs(t, err, context.Canceled) return nil, err }, - ) + ).MaxTimes(1) return New(subnetID, state, client) }, unsignedMsg: unsignedMsg, From fa04e3d40ebf7c79804766065bddb210a111fdf7 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Mon, 16 Oct 2023 20:53:19 +0300 Subject: [PATCH 43/52] rename stuttering files (#957) --- warp/{warp_client.go => client.go} | 0 warp/{warp_client_fetcher.go => fetcher.go} | 0 warp/{warp_service.go => service.go} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename warp/{warp_client.go => client.go} (100%) rename warp/{warp_client_fetcher.go => fetcher.go} (100%) rename warp/{warp_service.go => service.go} (100%) diff --git a/warp/warp_client.go b/warp/client.go similarity index 100% rename from warp/warp_client.go rename to warp/client.go diff --git a/warp/warp_client_fetcher.go b/warp/fetcher.go similarity index 100% rename from warp/warp_client_fetcher.go rename to warp/fetcher.go diff --git a/warp/warp_service.go b/warp/service.go similarity index 100% rename from warp/warp_service.go rename to warp/service.go From adbd63e4deb8c29c8437c501210aa682824aa516 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Mon, 16 Oct 2023 14:12:26 -0400 Subject: [PATCH 44/52] Remove outdated readme (#955) --- warp/payload/README.md | 46 ------------------------------------------ 1 file changed, 46 deletions(-) delete mode 100644 warp/payload/README.md diff --git a/warp/payload/README.md b/warp/payload/README.md deleted file mode 100644 index 3db2a0e751..0000000000 --- a/warp/payload/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Payload - -An Avalanche Unsigned Warp Message already includes a `networkID`, `sourceChainID`, and `payload` field. The `payload` field is parsed into one of the types included in this package to be handled by the EVM. - -## AddressedPayload - -AddressedPayload: - -```text -+---------------------+----------+-------------------+ -| codecID : uint16 | 2 bytes | -+---------------------+----------+-------------------+ -| typeID : uint32 | 4 bytes | -+---------------------+----------+-------------------+ -| sourceAddress : [20]byte | 20 bytes | -+---------------------+----------+-------------------+ -| payload : []byte | 4 + len(payload) | -+---------------------+----------+-------------------+ - | 30 + len(payload) | - +-------------------+ -``` - -- `codecID` is the codec version used to serialize the payload and is hardcoded to `0x0000` -- `typeID` is the payload type identifier and is `0x00000000` for `AddressedPayload` -- `sourceAddress` is the address that called `sendWarpPrecompile` on the source chain -- `payload` is an arbitrary byte array payload - -## BlockHashPayload - -BlockHashPayload: - -```text -+-----------------+----------+-----------+ -| codecID : uint16 | 2 bytes | -+-----------------+----------+-----------+ -| typeID : uint32 | 4 bytes | -+-----------------+----------+-----------+ -| blockHash : [32]byte | 32 bytes | -+-----------------+----------+-----------+ - | 38 bytes | - +-----------+ -``` - -- `codecID` is the codec version used to serialize the payload and is hardcoded to `0x0000` -- `typeID` is the payload type identifier and is `0x00000001` for `BlockHashPayload` -- `blockHash` is a blockHash from the `sourceChainID`. A signed block hash payload indicates that the signer has accepted the block on the source chain. From ef7a85d32a9fb0638e159b72e21222fc53bf70e1 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 18 Oct 2023 11:35:41 +0300 Subject: [PATCH 45/52] Return message ID (#930) * fix warp event packing * add hardhat tests for warp * remove destination fields from test * parse from event data * move setup to beforesuite * add message id to events + return types * fix test * fix comment * format example warp sol --- contracts/contracts/ExampleWarp.sol | 100 +++++++++--------- .../contracts/interfaces/IWarpMessenger.sol | 72 ++++++------- contracts/test/warp.ts | 7 +- plugin/evm/vm_warp_test.go | 12 +++ x/warp/README.md | 5 +- x/warp/contract.abi | 14 ++- x/warp/contract.go | 31 +++++- x/warp/contract_test.go | 20 +++- 8 files changed, 165 insertions(+), 96 deletions(-) diff --git a/contracts/contracts/ExampleWarp.sol b/contracts/contracts/ExampleWarp.sol index 8701681fd9..b6247058ef 100644 --- a/contracts/contracts/ExampleWarp.sol +++ b/contracts/contracts/ExampleWarp.sol @@ -5,54 +5,54 @@ pragma experimental ABIEncoderV2; import "./interfaces/IWarpMessenger.sol"; contract ExampleWarp { - address constant WARP_ADDRESS = 0x0200000000000000000000000000000000000005; - IWarpMessenger warp = IWarpMessenger(WARP_ADDRESS); - - // sendWarpMessage sends a warp message containing the payload - function sendWarpMessage(bytes calldata payload) external { - warp.sendWarpMessage(payload); - } - - // validateWarpMessage retrieves the warp message attached to the transaction and verifies all of its attributes. - function validateWarpMessage( - uint32 index, - bytes32 sourceChainID, - address originSenderAddress, - bytes calldata payload - ) external view { - (WarpMessage memory message, bool valid) = warp.getVerifiedWarpMessage(index); - require(valid); - require(message.sourceChainID == sourceChainID); - require(message.originSenderAddress == originSenderAddress); - require(keccak256(message.payload) == keccak256(payload)); - } - - function validateInvalidWarpMessage(uint32 index) external view { - (WarpMessage memory message, bool valid) = warp.getVerifiedWarpMessage(index); - require(!valid); - require(message.sourceChainID == bytes32(0)); - require(message.originSenderAddress == address(0)); - require(keccak256(message.payload) == keccak256(bytes(""))); - } - - // validateWarpBlockHash retrieves the warp block hash attached to the transaction and verifies it matches the - // expected block hash. - function validateWarpBlockHash(uint32 index, bytes32 sourceChainID, bytes32 blockHash) external view { - (WarpBlockHash memory warpBlockHash, bool valid) = warp.getVerifiedWarpBlockHash(index); - require(valid); - require(warpBlockHash.sourceChainID == sourceChainID); - require(warpBlockHash.blockHash == blockHash); - } - - function validateInvalidWarpBlockHash(uint32 index) external view { - (WarpBlockHash memory warpBlockHash, bool valid) = warp.getVerifiedWarpBlockHash(index); - require(!valid); - require(warpBlockHash.sourceChainID == bytes32(0)); - require(warpBlockHash.blockHash == bytes32(0)); - } - - // validateGetBlockchainID checks that the blockchainID returned by warp matches the argument - function validateGetBlockchainID(bytes32 blockchainID) external view { - require(blockchainID == warp.getBlockchainID()); - } + address constant WARP_ADDRESS = 0x0200000000000000000000000000000000000005; + IWarpMessenger warp = IWarpMessenger(WARP_ADDRESS); + + // sendWarpMessage sends a warp message containing the payload + function sendWarpMessage(bytes calldata payload) external { + warp.sendWarpMessage(payload); + } + + // validateWarpMessage retrieves the warp message attached to the transaction and verifies all of its attributes. + function validateWarpMessage( + uint32 index, + bytes32 sourceChainID, + address originSenderAddress, + bytes calldata payload + ) external view { + (WarpMessage memory message, bool valid) = warp.getVerifiedWarpMessage(index); + require(valid); + require(message.sourceChainID == sourceChainID); + require(message.originSenderAddress == originSenderAddress); + require(keccak256(message.payload) == keccak256(payload)); + } + + function validateInvalidWarpMessage(uint32 index) external view { + (WarpMessage memory message, bool valid) = warp.getVerifiedWarpMessage(index); + require(!valid); + require(message.sourceChainID == bytes32(0)); + require(message.originSenderAddress == address(0)); + require(keccak256(message.payload) == keccak256(bytes(""))); + } + + // validateWarpBlockHash retrieves the warp block hash attached to the transaction and verifies it matches the + // expected block hash. + function validateWarpBlockHash(uint32 index, bytes32 sourceChainID, bytes32 blockHash) external view { + (WarpBlockHash memory warpBlockHash, bool valid) = warp.getVerifiedWarpBlockHash(index); + require(valid); + require(warpBlockHash.sourceChainID == sourceChainID); + require(warpBlockHash.blockHash == blockHash); + } + + function validateInvalidWarpBlockHash(uint32 index) external view { + (WarpBlockHash memory warpBlockHash, bool valid) = warp.getVerifiedWarpBlockHash(index); + require(!valid); + require(warpBlockHash.sourceChainID == bytes32(0)); + require(warpBlockHash.blockHash == bytes32(0)); + } + + // validateGetBlockchainID checks that the blockchainID returned by warp matches the argument + function validateGetBlockchainID(bytes32 blockchainID) external view { + require(blockchainID == warp.getBlockchainID()); + } } diff --git a/contracts/contracts/interfaces/IWarpMessenger.sol b/contracts/contracts/interfaces/IWarpMessenger.sol index 5c67e0d1d4..0a77d36640 100644 --- a/contracts/contracts/interfaces/IWarpMessenger.sol +++ b/contracts/contracts/interfaces/IWarpMessenger.sol @@ -6,46 +6,46 @@ pragma solidity ^0.8.0; struct WarpMessage { - bytes32 sourceChainID; - address originSenderAddress; - bytes payload; + bytes32 sourceChainID; + address originSenderAddress; + bytes payload; } struct WarpBlockHash { - bytes32 sourceChainID; - bytes32 blockHash; + bytes32 sourceChainID; + bytes32 blockHash; } interface IWarpMessenger { - event SendWarpMessage(address indexed sender, bytes message); - - // sendWarpMessage emits a request for the subnet to send a warp message from [msg.sender] - // with the specified parameters. - // This emits a SendWarpMessage log from the precompile. When the corresponding block is accepted - // the Accept hook of the Warp precompile is invoked with all accepted logs emitted by the Warp - // precompile. - // Each validator then adds the UnsignedWarpMessage encoded in the log to the set of messages - // it is willing to sign for an off-chain relayer to aggregate Warp signatures. - function sendWarpMessage(bytes calldata payload) external; - - // getVerifiedWarpMessage parses the pre-verified warp message in the - // predicate storage slots as a WarpMessage and returns it to the caller. - // If the message exists and passes verification, returns the verified message - // and true. - // Otherwise, returns false and the empty value for the message. - function getVerifiedWarpMessage(uint32 index) external view returns (WarpMessage calldata message, bool valid); - - // getVerifiedWarpBlockHash parses the pre-verified WarpBlockHash message in the - // predicate storage slots as a WarpBlockHash message and returns it to the caller. - // If the message exists and passes verification, returns the verified message - // and true. - // Otherwise, returns false and the empty value for the message. - function getVerifiedWarpBlockHash( - uint32 index - ) external view returns (WarpBlockHash calldata warpBlockHash, bool valid); - - // getBlockchainID returns the snow.Context BlockchainID of this chain. - // This blockchainID is the hash of the transaction that created this blockchain on the P-Chain - // and is not related to the Ethereum ChainID. - function getBlockchainID() external view returns (bytes32 blockchainID); + event SendWarpMessage(address indexed sender, bytes32 indexed messageID, bytes message); + + // sendWarpMessage emits a request for the subnet to send a warp message from [msg.sender] + // with the specified parameters. + // This emits a SendWarpMessage log from the precompile. When the corresponding block is accepted + // the Accept hook of the Warp precompile is invoked with all accepted logs emitted by the Warp + // precompile. + // Each validator then adds the UnsignedWarpMessage encoded in the log to the set of messages + // it is willing to sign for an off-chain relayer to aggregate Warp signatures. + function sendWarpMessage(bytes calldata payload) external returns (bytes32 messageID); + + // getVerifiedWarpMessage parses the pre-verified warp message in the + // predicate storage slots as a WarpMessage and returns it to the caller. + // If the message exists and passes verification, returns the verified message + // and true. + // Otherwise, returns false and the empty value for the message. + function getVerifiedWarpMessage(uint32 index) external view returns (WarpMessage calldata message, bool valid); + + // getVerifiedWarpBlockHash parses the pre-verified WarpBlockHash message in the + // predicate storage slots as a WarpBlockHash message and returns it to the caller. + // If the message exists and passes verification, returns the verified message + // and true. + // Otherwise, returns false and the empty value for the message. + function getVerifiedWarpBlockHash( + uint32 index + ) external view returns (WarpBlockHash calldata warpBlockHash, bool valid); + + // getBlockchainID returns the snow.Context BlockchainID of this chain. + // This blockchainID is the hash of the transaction that created this blockchain on the P-Chain + // and is not related to the Ethereum ChainID. + function getBlockchainID() external view returns (bytes32 blockchainID); } diff --git a/contracts/test/warp.ts b/contracts/test/warp.ts index a6b847a829..ae30b397ad 100644 --- a/contracts/test/warp.ts +++ b/contracts/test/warp.ts @@ -9,7 +9,7 @@ import { import { ethers } from "hardhat" const WARP_ADDRESS = "0x0200000000000000000000000000000000000005"; -let senderAddress = process.env["SENDER_ADDRESS"]; +let senderAddress = process.env["SENDER_ADDRESS"]; // Expected to be a hex string let payload = process.env["PAYLOAD"]; let expectedUnsignedMessage = process.env["EXPECTED_UNSIGNED_MESSAGE"]; @@ -26,9 +26,12 @@ describe("IWarpMessenger", function () { it("contract should be to send warp message", async function () { console.log(`Sending warp message with payload ${payload}, expected unsigned message ${expectedUnsignedMessage}`); + // Get ID of payload by taking sha256 of unsigned message + let messageID = ethers.utils.sha256(expectedUnsignedMessage); + await expect(contract.sendWarpMessage(payload)) .to.emit(contract, 'SendWarpMessage') - .withArgs(senderAddress, expectedUnsignedMessage); + .withArgs(senderAddress, messageID, expectedUnsignedMessage); }) it("should be able to fetch correct blockchain ID", async function () { diff --git a/plugin/evm/vm_warp_test.go b/plugin/evm/vm_warp_test.go index ff98ed2405..0d0745b984 100644 --- a/plugin/evm/vm_warp_test.go +++ b/plugin/evm/vm_warp_test.go @@ -66,6 +66,17 @@ func TestSendWarpMessage(t *testing.T) { warpSendMessageInput, err := warp.PackSendWarpMessage(payloadData) require.NoError(err) + addressedPayload, err := payload.NewAddressedCall( + testEthAddrs[0].Bytes(), + payloadData, + ) + require.NoError(err) + expectedUnsignedMessage, err := avalancheWarp.NewUnsignedMessage( + vm.ctx.NetworkID, + vm.ctx.ChainID, + addressedPayload.Bytes(), + ) + require.NoError(err) // Submit a transaction to trigger sending a warp message tx0 := types.NewTransaction(uint64(0), warp.ContractAddress, big.NewInt(1), 100_000, big.NewInt(testMinGasPrice), warpSendMessageInput) @@ -93,6 +104,7 @@ func TestSendWarpMessage(t *testing.T) { expectedTopics := []common.Hash{ warp.WarpABI.Events["SendWarpMessage"].ID, testEthAddrs[0].Hash(), + common.Hash(expectedUnsignedMessage.ID()), } require.Equal(expectedTopics, receipts[0].Logs[0].Topics) logData := receipts[0].Logs[0].Data diff --git a/x/warp/README.md b/x/warp/README.md index b7aa5fd4a2..1a72e0d476 100644 --- a/x/warp/README.md +++ b/x/warp/README.md @@ -44,13 +44,14 @@ The Warp Precompile is broken down into three functions defined in the Solidity Calling this function will issue a `SendWarpMessage` event from the Warp Precompile. Since the EVM limits the number of topics to 4 including the EventID, this message includes only the topics that would be expected to help filter messages emitted from the Warp Precompile the most. -Specifically, the `payload` is not emitted as a topic because each topic must be encoded as a hash. It could include the warp `messageID` as a topic, but that would not add more information. Therefore, we opt to take advantage of each possible topic to maximize the possible filtering for emitted Warp Messages. +Specifically, the `payload` is not emitted as a topic because each topic must be encoded as a hash. Therefore, we opt to take advantage of each possible topic to maximize the possible filtering for emitted Warp Messages. Additionally, the `SourceChainID` is excluded because anyone parsing the chain can be expected to already know the blockchainID. Therefore, the `SendWarpMessage` event includes the indexable attributes: - `sender` +- The `messageID` of the unsigned message (sha256 of the unsigned message) -The actual `message` is the entire [Avalanche Warp Unsigned Message](https://github.com/ava-labs/avalanchego/blob/master/vms/platformvm/warp/unsigned_message.go#L14) including the Subnet-EVM [Addressed Payload](../../../warp/payload/payload.go). +The actual `message` is the entire [Avalanche Warp Unsigned Message](https://github.com/ava-labs/avalanchego/blob/master/vms/platformvm/warp/unsigned_message.go#L14) including the Subnet-EVM [Addressed Payload](../../../warp/payload/payload.go). This is emitted as the unindexed data in the log. #### getVerifiedMessage diff --git a/x/warp/contract.abi b/x/warp/contract.abi index 04aa755c32..771103ecbc 100644 --- a/x/warp/contract.abi +++ b/x/warp/contract.abi @@ -8,6 +8,12 @@ "name": "sender", "type": "address" }, + { + "indexed": true, + "internalType": "bytes32", + "name": "messageID", + "type": "bytes32" + }, { "indexed": false, "internalType": "bytes", @@ -117,7 +123,13 @@ } ], "name": "sendWarpMessage", - "outputs": [], + "outputs": [ + { + "internalType": "bytes32", + "name": "messageID", + "type": "bytes32" + } + ], "stateMutability": "nonpayable", "type": "function" } diff --git a/x/warp/contract.go b/x/warp/contract.go index 2486ea2ddb..43109b4317 100644 --- a/x/warp/contract.go +++ b/x/warp/contract.go @@ -204,6 +204,23 @@ func PackSendWarpMessage(payloadData []byte) ([]byte, error) { return WarpABI.Pack("sendWarpMessage", payloadData) } +// PackSendWarpMessageOutput attempts to pack given messageID of type common.Hash +// to conform the ABI outputs. +func PackSendWarpMessageOutput(messageID common.Hash) ([]byte, error) { + return WarpABI.PackOutput("sendWarpMessage", messageID) +} + +// UnpackSendWarpMessageOutput attempts to unpack given [output] into the common.Hash type output +// assumes that [output] does not include selector (omits first 4 func signature bytes) +func UnpackSendWarpMessageOutput(output []byte) (common.Hash, error) { + res, err := WarpABI.Unpack("sendWarpMessage", output) + if err != nil { + return common.Hash{}, err + } + unpacked := *abi.ConvertType(res[0], new(common.Hash)).(*common.Hash) + return unpacked, nil +} + // sendWarpMessage constructs an Avalanche Warp Message containing an AddressedPayload and emits a log to signal validators that they should // be willing to sign this message. func sendWarpMessage(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { @@ -252,6 +269,7 @@ func sendWarpMessage(accessibleState contract.AccessibleState, caller common.Add // Add a log to be handled if this action is finalized. topics, data, err := PackSendWarpMessageEvent( sourceAddress, + common.Hash(unsignedWarpMessage.ID()), unsignedWarpMessage.Bytes(), ) if err != nil { @@ -264,13 +282,18 @@ func sendWarpMessage(accessibleState contract.AccessibleState, caller common.Add accessibleState.GetBlockContext().Number().Uint64(), ) - // Return an empty output and the remaining gas - return []byte{}, remainingGas, nil + packed, err := PackSendWarpMessageOutput(common.Hash(unsignedWarpMessage.ID())) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed message ID and the remaining gas + return packed, remainingGas, nil } // PackSendWarpMessageEvent packs the given arguments into SendWarpMessage events including topics and data. -func PackSendWarpMessageEvent(sourceAddress common.Address, unsignedMessageBytes []byte) ([]common.Hash, []byte, error) { - return WarpABI.PackEvent("SendWarpMessage", sourceAddress, unsignedMessageBytes) +func PackSendWarpMessageEvent(sourceAddress common.Address, unsignedMessageID common.Hash, unsignedMessageBytes []byte) ([]common.Hash, []byte, error) { + return WarpABI.PackEvent("SendWarpMessage", sourceAddress, unsignedMessageID, unsignedMessageBytes) } // UnpackSendWarpEventDataToMessage attempts to unpack event [data] as warp.UnsignedMessage. diff --git a/x/warp/contract_test.go b/x/warp/contract_test.go index e78ba4f20d..0f41b504f1 100644 --- a/x/warp/contract_test.go +++ b/x/warp/contract_test.go @@ -91,6 +91,17 @@ func TestSendWarpMessage(t *testing.T) { sendWarpMessageInput, err := PackSendWarpMessage(sendWarpMessagePayload) require.NoError(t, err) + sendWarpMessageAddressedPayload, err := payload.NewAddressedCall( + callerAddr.Bytes(), + sendWarpMessagePayload, + ) + require.NoError(t, err) + unsignedWarpMessage, err := warp.NewUnsignedMessage( + defaultSnowCtx.NetworkID, + blockchainID, + sendWarpMessageAddressedPayload.Bytes(), + ) + require.NoError(t, err) tests := map[string]testutils.PrecompileTest{ "send warp message readOnly": { @@ -128,7 +139,13 @@ func TestSendWarpMessage(t *testing.T) { InputFn: func(t testing.TB) []byte { return sendWarpMessageInput }, SuppliedGas: SendWarpMessageGasCost + uint64(len(sendWarpMessageInput[4:])*int(SendWarpMessageGasCostPerByte)), ReadOnly: false, - ExpectedRes: []byte{}, + ExpectedRes: func() []byte { + bytes, err := PackSendWarpMessageOutput(common.Hash(unsignedWarpMessage.ID())) + if err != nil { + panic(err) + } + return bytes + }(), AfterHook: func(t testing.TB, state contract.StateDB) { logsData := state.GetLogData() require.Len(t, logsData, 1) @@ -733,6 +750,7 @@ func TestPackEvents(t *testing.T) { _, data, err := PackSendWarpMessageEvent( sourceAddress, + common.Hash(unsignedMsg.ID()), unsignedWarpMessage.Bytes(), ) require.NoError(t, err) From 00e76fb3210312dc9217d1a611a4e8c2716c8e41 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Wed, 18 Oct 2023 09:40:34 -0400 Subject: [PATCH 46/52] plugin/evm: update type cast to skip call to Bytes() (#958) Co-authored-by: Ceyhun Onur --- plugin/evm/block.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/block.go b/plugin/evm/block.go index a600292849..2422fe1aff 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -132,7 +132,7 @@ func (b *Block) handlePrecompileAccept(rules *params.Rules, sharedMemoryWriter * // If Warp is enabled, add the block hash as an unsigned message to the warp backend. if rules.IsPrecompileEnabled(warp.ContractAddress) { - blockHashPayload, err := payload.NewHash(ids.ID(b.ethBlock.Hash().Bytes())) + blockHashPayload, err := payload.NewHash(ids.ID(b.ethBlock.Hash())) if err != nil { return fmt.Errorf("failed to create block hash payload: %w", err) } From c62acc5101b77e253a910457134bc39a8ee76b6a Mon Sep 17 00:00:00 2001 From: Anusha <63559942+anusha-ctrl@users.noreply.github.com> Date: Wed, 18 Oct 2023 07:56:06 -0700 Subject: [PATCH 47/52] Improve TPS on E2E Load Test (#947) * init * make fee changes * change simulator settings * Correct numbers * correct header * correct --- scripts/run_simulator.sh | 8 ++-- tests/load/genesis/genesis.json | 85 ++++++++++++++++----------------- 2 files changed, 47 insertions(+), 46 deletions(-) diff --git a/scripts/run_simulator.sh b/scripts/run_simulator.sh index d1fb6c4c41..41e97aec0a 100755 --- a/scripts/run_simulator.sh +++ b/scripts/run_simulator.sh @@ -33,10 +33,12 @@ run_simulator() { ./cmd/simulator/simulator \ --endpoints=$RPC_ENDPOINTS \ --key-dir=./cmd/simulator/.simulator/keys \ - --timeout=30s \ + --timeout=300s \ --workers=1 \ - --max-fee-cap=300 \ - --max-tip-cap=100 + --txs-per-worker=50000 \ + --batch-size=50000 \ + --max-fee-cap=1000000 \ + --max-tip-cap=10000 } run_simulator diff --git a/tests/load/genesis/genesis.json b/tests/load/genesis/genesis.json index cac224d1a2..be5abf0f2c 100644 --- a/tests/load/genesis/genesis.json +++ b/tests/load/genesis/genesis.json @@ -1,45 +1,44 @@ { - "config": { - "chainId": 99999, - "homesteadBlock": 0, - "eip150Block": 0, - "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0, - "constantinopleBlock": 0, - "petersburgBlock": 0, - "istanbulBlock": 0, - "muirGlacierBlock": 0, - "subnetEVMTimestamp": 0, - "feeConfig": { - "gasLimit": 20000000, - "minBaseFee": 1000000000, - "targetGas": 100000000, - "baseFeeChangeDenominator": 48, - "minBlockGasCost": 0, - "maxBlockGasCost": 10000000, - "targetBlockRate": 2, - "blockGasCostStep": 500000 - } + "config": { + "chainId": 99999, + "homesteadBlock": 0, + "eip150Block": 0, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "subnetEVMTimestamp": 0, + "feeConfig": { + "gasLimit": 200000000, + "minBaseFee": 1000000000, + "targetGas": 400000000, + "baseFeeChangeDenominator": 48, + "minBlockGasCost": 0, + "maxBlockGasCost": 10000000, + "targetBlockRate": 2, + "blockGasCostStep": 500000 + } + }, + "alloc": { + "8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": { + "balance": "0x52B7D2DCC80CD2E4000000" }, - "alloc": { - "8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": { - "balance": "0x52B7D2DCC80CD2E4000000" - }, - "0x0Fa8EA536Be85F32724D57A37758761B86416123": { - "balance": "0x52B7D2DCC80CD2E4000000" - } - }, - "nonce": "0x0", - "timestamp": "0x0", - "extraData": "0x00", - "gasLimit": "0x1312D00", - "difficulty": "0x0", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase": "0x0000000000000000000000000000000000000000", - "number": "0x0", - "gasUsed": "0x0", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" - } - \ No newline at end of file + "0x0Fa8EA536Be85F32724D57A37758761B86416123": { + "balance": "0x52B7D2DCC80CD2E4000000" + } + }, + "nonce": "0x0", + "timestamp": "0x0", + "extraData": "0x00", + "gasLimit": "0xBEBC200", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" +} From 97194d68689318ad35c2c7e63b185050d40f77c8 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Wed, 18 Oct 2023 11:04:36 -0400 Subject: [PATCH 48/52] x/warp: update PredicateGas to require warp message contains a valid payload (#945) * x/warp: add check for valid payload in PredicateGas * x/warp: fix typo --- x/warp/config.go | 8 ++++++++ x/warp/predicate_test.go | 11 +++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/x/warp/config.go b/x/warp/config.go index a5bd8543ce..d39c2551f9 100644 --- a/x/warp/config.go +++ b/x/warp/config.go @@ -10,6 +10,7 @@ import ( "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ava-labs/subnet-evm/predicate" @@ -29,6 +30,7 @@ var ( errOverflowSignersGasCost = errors.New("overflow calculating warp signers gas cost") errInvalidPredicateBytes = errors.New("cannot unpack predicate bytes") errInvalidWarpMsg = errors.New("cannot unpack warp message") + errInvalidWarpMsgPayload = errors.New("cannot unpack warp message payload") errInvalidAddressedPayload = errors.New("cannot unpack addressed payload") errInvalidBlockHashPayload = errors.New("cannot unpack block hash payload") errCannotGetNumSigners = errors.New("cannot fetch num signers from warp message") @@ -147,6 +149,8 @@ func (c *Config) verifyWarpMessage(predicateContext *precompileconfig.PredicateC // 2. Size of the message // 3. Number of signers // 4. TODO: Lookup of the validator set +// +// If the payload of the warp message fails parsing, return a non-nil error invalidating the transaction. func (c *Config) PredicateGas(predicateBytes []byte) (uint64, error) { totalGas := GasCostPerSignatureVerification bytesGasCost, overflow := math.SafeMul(GasCostPerWarpMessageBytes, uint64(len(predicateBytes))) @@ -166,6 +170,10 @@ func (c *Config) PredicateGas(predicateBytes []byte) (uint64, error) { if err != nil { return 0, fmt.Errorf("%w: %s", errInvalidWarpMsg, err) } + _, err = payload.Parse(warpMessage.Payload) + if err != nil { + return 0, fmt.Errorf("%w: %s", errInvalidWarpMsgPayload, err) + } numSigners, err := warpMessage.Signature.NumSigners() if err != nil { diff --git a/x/warp/predicate_test.go b/x/warp/predicate_test.go index 79cf478815..1d98fe218c 100644 --- a/x/warp/predicate_test.go +++ b/x/warp/predicate_test.go @@ -232,7 +232,9 @@ func TestWarpMessageFromPrimaryNetwork(t *testing.T) { require := require.New(t) numKeys := 10 cChainID := ids.GenerateTestID() - unsignedMsg, err := avalancheWarp.NewUnsignedMessage(networkID, cChainID, []byte{1, 2, 3}) + addressedCall, err := payload.NewAddressedCall(utils.RandomBytes(20), utils.RandomBytes(100)) + require.NoError(err) + unsignedMsg, err := avalancheWarp.NewUnsignedMessage(networkID, cChainID, addressedCall.Bytes()) require.NoError(err) getValidatorsOutput := make(map[ids.NodeID]*validators.GetValidatorOutput) @@ -393,18 +395,19 @@ func TestInvalidAddressedPayload(t *testing.T) { }, StorageSlots: [][]byte{predicateBytes}, Gas: GasCostPerSignatureVerification + uint64(len(predicateBytes))*GasCostPerWarpMessageBytes + uint64(numKeys)*GasCostPerWarpSigner, - GasErr: nil, - PredicateRes: set.NewBits(0).Bytes(), + GasErr: errInvalidWarpMsgPayload, } test.Run(t) } func TestInvalidBitSet(t *testing.T) { + addressedCall, err := payload.NewAddressedCall(utils.RandomBytes(20), utils.RandomBytes(100)) + require.NoError(t, err) unsignedMsg, err := avalancheWarp.NewUnsignedMessage( networkID, sourceChainID, - []byte{1, 2, 3}, + addressedCall.Bytes(), ) require.NoError(t, err) From 46a0d3c6ea02b8a4d9a491f444793646c1d24029 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 19 Oct 2023 20:24:06 +0300 Subject: [PATCH 49/52] bump subnet-evm and avalanchego versions (#965) --- README.md | 1 + compatibility.json | 1 + go.mod | 14 +++++++------ go.sum | 28 +++++++++++++++----------- plugin/evm/admin.go | 16 +++++++++++++++ plugin/evm/service.go | 7 +++++++ plugin/evm/version.go | 2 +- plugin/evm/vm.go | 46 +++++++++++++++---------------------------- scripts/versions.sh | 2 +- 9 files changed, 67 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index a4c5f7dd0e..c3e8b641e3 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ The Subnet EVM runs in a separate process from the main AvalancheGo process and [v0.5.4] AvalancheGo@v1.10.9-v1.10.12 (Protocol Version: 28) [v0.5.5] AvalancheGo@v1.10.9-v1.10.12 (Protocol Version: 28) [v0.5.6] AvalancheGo@v1.10.9-v1.10.12 (Protocol Version: 28) +[v0.5.7] AvalancheGo@v1.10.13-v1.10.13 (Protocol Version: 29) ``` ## API diff --git a/compatibility.json b/compatibility.json index 58b436d01d..f3df6679de 100644 --- a/compatibility.json +++ b/compatibility.json @@ -1,5 +1,6 @@ { "rpcChainVMProtocolVersion": { + "v0.5.7": 29, "v0.5.6": 28, "v0.5.5": 28, "v0.5.4": 28, diff --git a/go.mod b/go.mod index 8fa6860b64..00a1d94e7e 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/VictoriaMetrics/fastcache v1.10.0 github.com/ava-labs/avalanche-network-runner v1.7.2 - github.com/ava-labs/avalanchego v1.10.12 + github.com/ava-labs/avalanchego v1.10.13 github.com/cespare/cp v0.1.0 github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 github.com/davecgh/go-spew v1.1.1 @@ -44,18 +44,18 @@ require ( github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa go.uber.org/mock v0.2.0 golang.org/x/crypto v0.14.0 - golang.org/x/sync v0.2.0 + golang.org/x/sync v0.3.0 golang.org/x/sys v0.13.0 golang.org/x/text v0.13.0 golang.org/x/time v0.1.0 - google.golang.org/protobuf v1.30.0 + google.golang.org/protobuf v1.31.0 ) require ( github.com/DataDog/zstd v1.5.2 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect - github.com/ava-labs/coreth v0.12.5-rc.6 // indirect + github.com/ava-labs/coreth v0.12.6-rc.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.7.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect @@ -148,8 +148,10 @@ require ( golang.org/x/net v0.17.0 // indirect golang.org/x/term v0.13.0 // indirect gonum.org/v1/gonum v0.11.0 // indirect - google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect - google.golang.org/grpc v1.56.0-dev // indirect + google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/grpc v1.58.3 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect diff --git a/go.sum b/go.sum index a58f39c055..f23ab9ef18 100644 --- a/go.sum +++ b/go.sum @@ -61,10 +61,10 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/ava-labs/avalanche-network-runner v1.7.2 h1:XFad/wZfYzDnqbLzPAPPRYU3a1Zc8QT8x5dtLTS3lUo= github.com/ava-labs/avalanche-network-runner v1.7.2/go.mod h1:naLveusSrP7YgTAqRykD1SyLOAUilCp9jGjk3MDxoPI= -github.com/ava-labs/avalanchego v1.10.12 h1:GmS2/4ugkpxV1sHq4EB+y/wsjhxCZxVgvWAlDRk6TSs= -github.com/ava-labs/avalanchego v1.10.12/go.mod h1:9fKHRV5IrmS+Y8hUEIzDPUEHPIuFm8olPPf40qE46ZQ= -github.com/ava-labs/coreth v0.12.5-rc.6 h1:OajGUyKkO5Q82XSuMa8T5UD6QywtCHUiZ4Tv3RFmRBU= -github.com/ava-labs/coreth v0.12.5-rc.6/go.mod h1:s5wVyy+5UCCk2m0Tq3jVmy0UqOpKBDYqRE13gInCJVs= +github.com/ava-labs/avalanchego v1.10.13 h1:LJQoV20Rw1/D/anP7RTTTmzgvwzx3DKEfe1BZp3Szxg= +github.com/ava-labs/avalanchego v1.10.13/go.mod h1:tbPGmDognRVE/6xqqal/fszBZlr0jyll3KXXO/7cuqo= +github.com/ava-labs/coreth v0.12.6-rc.2 h1:5P9/i3h6g2uUYf6BVhG8YrV6iqhov6vRxph6cvuKpvU= +github.com/ava-labs/coreth v0.12.6-rc.2/go.mod h1:sNbwitXv4AhLvWpSqy6V8yzkhGFeWBQFD31/xiRDJ5M= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -790,8 +790,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1013,8 +1013,12 @@ google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= +google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98/go.mod h1:S7mY02OqCJTD0E1OiQy1F72PWFB4bZJ87cAtLPYgDR0= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -1037,8 +1041,8 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.56.0-dev h1:3XdSkn+E4E0OxKEID50paHDwVA7cqZVolkHtMFaoQJA= -google.golang.org/grpc v1.56.0-dev/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= +google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1052,8 +1056,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/plugin/evm/admin.go b/plugin/evm/admin.go index 0028398ed5..fd8d7f8d6e 100644 --- a/plugin/evm/admin.go +++ b/plugin/evm/admin.go @@ -29,6 +29,9 @@ func NewAdminService(vm *VM, performanceDir string) *Admin { func (p *Admin) StartCPUProfiler(_ *http.Request, _ *struct{}, _ *api.EmptyReply) error { log.Info("Admin: StartCPUProfiler called") + p.vm.ctx.Lock.Lock() + defer p.vm.ctx.Lock.Unlock() + return p.profiler.StartCPUProfiler() } @@ -36,6 +39,9 @@ func (p *Admin) StartCPUProfiler(_ *http.Request, _ *struct{}, _ *api.EmptyReply func (p *Admin) StopCPUProfiler(r *http.Request, _ *struct{}, _ *api.EmptyReply) error { log.Info("Admin: StopCPUProfiler called") + p.vm.ctx.Lock.Lock() + defer p.vm.ctx.Lock.Unlock() + return p.profiler.StopCPUProfiler() } @@ -43,6 +49,9 @@ func (p *Admin) StopCPUProfiler(r *http.Request, _ *struct{}, _ *api.EmptyReply) func (p *Admin) MemoryProfile(_ *http.Request, _ *struct{}, _ *api.EmptyReply) error { log.Info("Admin: MemoryProfile called") + p.vm.ctx.Lock.Lock() + defer p.vm.ctx.Lock.Unlock() + return p.profiler.MemoryProfile() } @@ -50,6 +59,9 @@ func (p *Admin) MemoryProfile(_ *http.Request, _ *struct{}, _ *api.EmptyReply) e func (p *Admin) LockProfile(_ *http.Request, _ *struct{}, _ *api.EmptyReply) error { log.Info("Admin: LockProfile called") + p.vm.ctx.Lock.Lock() + defer p.vm.ctx.Lock.Unlock() + return p.profiler.LockProfile() } @@ -59,6 +71,10 @@ type SetLogLevelArgs struct { func (p *Admin) SetLogLevel(_ *http.Request, args *SetLogLevelArgs, reply *api.EmptyReply) error { log.Info("EVM: SetLogLevel called", "logLevel", args.Level) + + p.vm.ctx.Lock.Lock() + defer p.vm.ctx.Lock.Unlock() + if err := p.vm.logger.SetLogLevel(args.Level); err != nil { return fmt.Errorf("failed to parse log level: %w ", err) } diff --git a/plugin/evm/service.go b/plugin/evm/service.go index 54b5021a45..3d6761e4af 100644 --- a/plugin/evm/service.go +++ b/plugin/evm/service.go @@ -30,6 +30,10 @@ type GetAcceptedFrontReply struct { // GetAcceptedFront returns the last accepted block's hash and height func (api *SnowmanAPI) GetAcceptedFront(ctx context.Context) (*GetAcceptedFrontReply, error) { blk := api.vm.blockChain.LastConsensusAcceptedBlock() + + api.vm.ctx.Lock.Lock() + defer api.vm.ctx.Lock.Unlock() + return &GetAcceptedFrontReply{ Hash: blk.Hash(), Number: blk.Number(), @@ -40,6 +44,9 @@ func (api *SnowmanAPI) GetAcceptedFront(ctx context.Context) (*GetAcceptedFrontR func (api *SnowmanAPI) IssueBlock(ctx context.Context) error { log.Info("Issuing a new block") + api.vm.ctx.Lock.Lock() + defer api.vm.ctx.Lock.Unlock() + api.vm.builder.signalTxsReady() return nil } diff --git a/plugin/evm/version.go b/plugin/evm/version.go index 6de9e2df51..d5489ea05e 100644 --- a/plugin/evm/version.go +++ b/plugin/evm/version.go @@ -11,7 +11,7 @@ var ( // GitCommit is set by the build script GitCommit string // Version is the version of Subnet EVM - Version string = "v0.5.6" + Version string = "v0.5.7" ) func init() { diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index bd48b6638d..c1e561a9a1 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "math/big" + "net/http" "os" "path/filepath" "strings" @@ -678,9 +679,7 @@ func (vm *VM) initBlockBuilding() error { vm.shutdownWg.Done() }() - var ( - txGossipHandler p2p.Handler - ) + var txGossipHandler p2p.Handler txGossipHandler, err = gossip.NewHandler[*GossipTx](txPool, txGossipHandlerConfig, vm.sdkMetrics) if err != nil { @@ -895,10 +894,7 @@ func (vm *VM) Version(context.Context) (string, error) { // - The handler's functionality is defined by [service] // [service] should be a gorilla RPC service (see https://www.gorillatoolkit.org/pkg/rpc/v2) // - The name of the service is [name] -// - The LockOption is the first element of [lockOption] -// By default the LockOption is WriteLock -// [lockOption] should have either 0 or 1 elements. Elements beside the first are ignored. -func newHandler(name string, service interface{}, lockOption ...commonEng.LockOption) (*commonEng.HTTPHandler, error) { +func newHandler(name string, service interface{}) (http.Handler, error) { server := avalancheRPC.NewServer() server.RegisterCodec(avalancheJSON.NewCodec(), "application/json") server.RegisterCodec(avalancheJSON.NewCodec(), "application/json;charset=UTF-8") @@ -906,15 +902,11 @@ func newHandler(name string, service interface{}, lockOption ...commonEng.LockOp return nil, err } - var lock commonEng.LockOption = commonEng.WriteLock - if len(lockOption) != 0 { - lock = lockOption[0] - } - return &commonEng.HTTPHandler{LockOptions: lock, Handler: server}, nil + return server, server.RegisterService(service, name) } // CreateHandlers makes new http handlers that can handle API calls -func (vm *VM) CreateHandlers(context.Context) (map[string]*commonEng.HTTPHandler, error) { +func (vm *VM) CreateHandlers(context.Context) (map[string]http.Handler, error) { handler := rpc.NewServer(vm.config.APIMaxDuration.Duration) enabledAPIs := vm.config.EthAPIs() if err := attachEthService(handler, vm.eth.APIs(), enabledAPIs); err != nil { @@ -925,7 +917,7 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]*commonEng.HTTPHandler if err != nil { return nil, fmt.Errorf("failed to get primary alias for chain due to %w", err) } - apis := make(map[string]*commonEng.HTTPHandler) + apis := make(map[string]http.Handler) if vm.config.AdminAPIEnabled { adminAPI, err := newHandler("admin", NewAdminService(vm, os.ExpandEnv(fmt.Sprintf("%s_subnet_evm_performance_%s", vm.config.AdminAPIDir, primaryAlias)))) if err != nil { @@ -953,25 +945,19 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]*commonEng.HTTPHandler } log.Info(fmt.Sprintf("Enabled APIs: %s", strings.Join(enabledAPIs, ", "))) - apis[ethRPCEndpoint] = &commonEng.HTTPHandler{ - LockOptions: commonEng.NoLock, - Handler: handler, - } - apis[ethWSEndpoint] = &commonEng.HTTPHandler{ - LockOptions: commonEng.NoLock, - Handler: handler.WebsocketHandlerWithDuration( - []string{"*"}, - vm.config.APIMaxDuration.Duration, - vm.config.WSCPURefillRate.Duration, - vm.config.WSCPUMaxStored.Duration, - ), - } + apis[ethRPCEndpoint] = handler + apis[ethWSEndpoint] = handler.WebsocketHandlerWithDuration( + []string{"*"}, + vm.config.APIMaxDuration.Duration, + vm.config.WSCPURefillRate.Duration, + vm.config.WSCPUMaxStored.Duration, + ) return apis, nil } // CreateStaticHandlers makes new http handlers that can handle API calls -func (vm *VM) CreateStaticHandlers(context.Context) (map[string]*commonEng.HTTPHandler, error) { +func (vm *VM) CreateStaticHandlers(context.Context) (map[string]http.Handler, error) { server := avalancheRPC.NewServer() codec := cjson.NewCodec() server.RegisterCodec(codec, "application/json") @@ -981,8 +967,8 @@ func (vm *VM) CreateStaticHandlers(context.Context) (map[string]*commonEng.HTTPH return nil, err } - return map[string]*commonEng.HTTPHandler{ - "/rpc": {LockOptions: commonEng.NoLock, Handler: server}, + return map[string]http.Handler{ + "/rpc": server, }, nil } diff --git a/scripts/versions.sh b/scripts/versions.sh index 31b61a6fe5..cec3bad0b8 100644 --- a/scripts/versions.sh +++ b/scripts/versions.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Don't export them as they're used in the context of other calls -AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.10.12'} +AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.10.13'} AVALANCHEGO_VERSION=${AVALANCHEGO_VERSION:-$AVALANCHE_VERSION} GINKGO_VERSION=${GINKGO_VERSION:-'v2.2.0'} From 4d97c64ec7c2f9f934f3e9dc094158d0a1e44166 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Mon, 23 Oct 2023 15:18:10 -0400 Subject: [PATCH 50/52] warp: add block signature handler (#962) * warp: add block signature handler * warp/aggregator: handle block/message types in aggregator * warp/aggregator: refactor aggregator * tests/warp: update e2e tests * Fix lint * cleanup * add logs to e2e test * Fix bug * address comments * tests/warp: add const for num nodes per subnet --- plugin/evm/block.go | 18 -- plugin/evm/message/codec.go | 3 +- plugin/evm/message/handler.go | 9 +- plugin/evm/message/signature_request.go | 32 +++- plugin/evm/message/signature_request_test.go | 33 ++-- plugin/evm/network_handler.go | 8 +- plugin/evm/vm.go | 7 +- plugin/evm/vm_test.go | 62 ++++++- plugin/evm/vm_warp_test.go | 24 ++- tests/warp/warp_test.go | 177 ++++++++++++++----- warp/aggregator/aggregator.go | 66 ++----- warp/aggregator/aggregator_test.go | 158 +++-------------- warp/aggregator/signature_getter.go | 33 +++- warp/backend.go | 85 +++++++-- warp/backend_test.go | 76 ++++++-- warp/client.go | 36 ++-- warp/fetcher.go | 19 +- warp/handlers/signature_request.go | 50 +++++- warp/handlers/signature_request_test.go | 153 ++++++++++++++-- warp/handlers/stats.go | 55 ++++-- warp/service.go | 83 +++++++-- 21 files changed, 798 insertions(+), 389 deletions(-) diff --git a/plugin/evm/block.go b/plugin/evm/block.go index 2422fe1aff..9dc3b1eb23 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -13,20 +13,17 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" - "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ava-labs/subnet-evm/predicate" - "github.com/ava-labs/subnet-evm/x/warp" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/choices" "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" - avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" ) var ( @@ -130,21 +127,6 @@ func (b *Block) handlePrecompileAccept(rules *params.Rules, sharedMemoryWriter * } } - // If Warp is enabled, add the block hash as an unsigned message to the warp backend. - if rules.IsPrecompileEnabled(warp.ContractAddress) { - blockHashPayload, err := payload.NewHash(ids.ID(b.ethBlock.Hash())) - if err != nil { - return fmt.Errorf("failed to create block hash payload: %w", err) - } - unsignedMessage, err := avalancheWarp.NewUnsignedMessage(b.vm.ctx.NetworkID, b.vm.ctx.ChainID, blockHashPayload.Bytes()) - if err != nil { - return fmt.Errorf("failed to create unsigned message for block hash payload: %w", err) - } - if err := b.vm.warpBackend.AddMessage(unsignedMessage); err != nil { - return fmt.Errorf("failed to add block hash payload unsigned message: %w", err) - } - } - return nil } diff --git a/plugin/evm/message/codec.go b/plugin/evm/message/codec.go index 8b9a84d43a..91db9633ab 100644 --- a/plugin/evm/message/codec.go +++ b/plugin/evm/message/codec.go @@ -41,7 +41,8 @@ func init() { c.RegisterType(CodeResponse{}), // Warp request types - c.RegisterType(SignatureRequest{}), + c.RegisterType(MessageSignatureRequest{}), + c.RegisterType(BlockSignatureRequest{}), c.RegisterType(SignatureResponse{}), Codec.RegisterCodec(Version, c), diff --git a/plugin/evm/message/handler.go b/plugin/evm/message/handler.go index 659908aaee..b5933f28f3 100644 --- a/plugin/evm/message/handler.go +++ b/plugin/evm/message/handler.go @@ -38,7 +38,8 @@ type RequestHandler interface { HandleTrieLeafsRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, leafsRequest LeafsRequest) ([]byte, error) HandleBlockRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, blockRequest BlockRequest) ([]byte, error) HandleCodeRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, codeRequest CodeRequest) ([]byte, error) - HandleSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest SignatureRequest) ([]byte, error) + HandleMessageSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest MessageSignatureRequest) ([]byte, error) + HandleBlockSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest BlockSignatureRequest) ([]byte, error) } // ResponseHandler handles response for a sent request @@ -64,7 +65,11 @@ func (NoopRequestHandler) HandleCodeRequest(ctx context.Context, nodeID ids.Node return nil, nil } -func (NoopRequestHandler) HandleSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest SignatureRequest) ([]byte, error) { +func (NoopRequestHandler) HandleMessageSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest MessageSignatureRequest) ([]byte, error) { + return nil, nil +} + +func (NoopRequestHandler) HandleBlockSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest BlockSignatureRequest) ([]byte, error) { return nil, nil } diff --git a/plugin/evm/message/signature_request.go b/plugin/evm/message/signature_request.go index 3ed8b64829..127716617f 100644 --- a/plugin/evm/message/signature_request.go +++ b/plugin/evm/message/signature_request.go @@ -11,22 +11,38 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto/bls" ) -var _ Request = SignatureRequest{} +var ( + _ Request = MessageSignatureRequest{} + _ Request = BlockSignatureRequest{} +) -// SignatureRequest is used to request a warp message's signature. -type SignatureRequest struct { +// MessageSignatureRequest is used to request a warp message's signature. +type MessageSignatureRequest struct { MessageID ids.ID `serialize:"true"` } -func (s SignatureRequest) String() string { - return fmt.Sprintf("SignatureRequest(MessageID=%s)", s.MessageID.String()) +func (s MessageSignatureRequest) String() string { + return fmt.Sprintf("MessageSignatureRequest(MessageID=%s)", s.MessageID.String()) +} + +func (s MessageSignatureRequest) Handle(ctx context.Context, nodeID ids.NodeID, requestID uint32, handler RequestHandler) ([]byte, error) { + return handler.HandleMessageSignatureRequest(ctx, nodeID, requestID, s) +} + +// BlockSignatureRequest is used to request a warp message's signature. +type BlockSignatureRequest struct { + BlockID ids.ID `serialize:"true"` +} + +func (s BlockSignatureRequest) String() string { + return fmt.Sprintf("BlockSignatureRequest(BlockID=%s)", s.BlockID.String()) } -func (s SignatureRequest) Handle(ctx context.Context, nodeID ids.NodeID, requestID uint32, handler RequestHandler) ([]byte, error) { - return handler.HandleSignatureRequest(ctx, nodeID, requestID, s) +func (s BlockSignatureRequest) Handle(ctx context.Context, nodeID ids.NodeID, requestID uint32, handler RequestHandler) ([]byte, error) { + return handler.HandleBlockSignatureRequest(ctx, nodeID, requestID, s) } -// SignatureResponse is the response to a SignatureRequest. +// SignatureResponse is the response to a BlockSignatureRequest or MessageSignatureRequest. // The response contains a BLS signature of the requested message, signed by the responding node's BLS private key. type SignatureResponse struct { Signature [bls.SignatureLen]byte `serialize:"true"` diff --git a/plugin/evm/message/signature_request_test.go b/plugin/evm/message/signature_request_test.go index 9e4c2fd96e..59614fbb2e 100644 --- a/plugin/evm/message/signature_request_test.go +++ b/plugin/evm/message/signature_request_test.go @@ -13,27 +13,40 @@ import ( "github.com/stretchr/testify/require" ) -// TestMarshalSignatureRequest asserts that the structure or serialization logic hasn't changed, primarily to +// TestMarshalMessageSignatureRequest asserts that the structure or serialization logic hasn't changed, primarily to // ensure compatibility with the network. -func TestMarshalSignatureRequest(t *testing.T) { - messageIDBytes, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000000") +func TestMarshalMessageSignatureRequest(t *testing.T) { + signatureRequest := MessageSignatureRequest{ + MessageID: ids.ID{68, 79, 70, 65, 72, 73, 64, 107}, + } + + base64MessageSignatureRequest := "AABET0ZBSElAawAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + signatureRequestBytes, err := Codec.Marshal(Version, signatureRequest) require.NoError(t, err) - messageID, err := ids.ToID(messageIDBytes) + require.Equal(t, base64MessageSignatureRequest, base64.StdEncoding.EncodeToString(signatureRequestBytes)) + + var s MessageSignatureRequest + _, err = Codec.Unmarshal(signatureRequestBytes, &s) require.NoError(t, err) + require.Equal(t, signatureRequest.MessageID, s.MessageID) +} - signatureRequest := SignatureRequest{ - MessageID: messageID, +// TestMarshalBlockSignatureRequest asserts that the structure or serialization logic hasn't changed, primarily to +// ensure compatibility with the network. +func TestMarshalBlockSignatureRequest(t *testing.T) { + signatureRequest := BlockSignatureRequest{ + BlockID: ids.ID{68, 79, 70, 65, 72, 73, 64, 107}, } - base64SignatureRequest := "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + base64BlockSignatureRequest := "AABET0ZBSElAawAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" signatureRequestBytes, err := Codec.Marshal(Version, signatureRequest) require.NoError(t, err) - require.Equal(t, base64SignatureRequest, base64.StdEncoding.EncodeToString(signatureRequestBytes)) + require.Equal(t, base64BlockSignatureRequest, base64.StdEncoding.EncodeToString(signatureRequestBytes)) - var s SignatureRequest + var s BlockSignatureRequest _, err = Codec.Unmarshal(signatureRequestBytes, &s) require.NoError(t, err) - require.Equal(t, signatureRequest.MessageID, s.MessageID) + require.Equal(t, signatureRequest.BlockID, s.BlockID) } // TestMarshalSignatureResponse asserts that the structure or serialization logic hasn't changed, primarily to diff --git a/plugin/evm/network_handler.go b/plugin/evm/network_handler.go index f0d68bc4d6..21bbf83d7e 100644 --- a/plugin/evm/network_handler.go +++ b/plugin/evm/network_handler.go @@ -56,6 +56,10 @@ func (n networkHandler) HandleCodeRequest(ctx context.Context, nodeID ids.NodeID return n.codeRequestHandler.OnCodeRequest(ctx, nodeID, requestID, codeRequest) } -func (n networkHandler) HandleSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest message.SignatureRequest) ([]byte, error) { - return n.signatureRequestHandler.OnSignatureRequest(ctx, nodeID, requestID, signatureRequest) +func (n networkHandler) HandleMessageSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, messageSignatureRequest message.MessageSignatureRequest) ([]byte, error) { + return n.signatureRequestHandler.OnMessageSignatureRequest(ctx, nodeID, requestID, messageSignatureRequest) +} + +func (n networkHandler) HandleBlockSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, blockSignatureRequest message.BlockSignatureRequest) ([]byte, error) { + return n.signatureRequestHandler.OnBlockSignatureRequest(ctx, nodeID, requestID, blockSignatureRequest) } diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index c1e561a9a1..adebe9157a 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -42,7 +42,6 @@ import ( "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/aggregator" warpValidators "github.com/ava-labs/subnet-evm/warp/validators" // Force-load tracer engine to trigger registration @@ -472,7 +471,7 @@ func (vm *VM) Initialize( vm.client = peer.NewNetworkClient(vm.Network) // initialize warp backend - vm.warpBackend = warp.NewBackend(vm.ctx.WarpSigner, vm.warpDB, warpSignatureCacheSize) + vm.warpBackend = warp.NewBackend(vm.ctx.NetworkID, vm.ctx.ChainID, vm.ctx.WarpSigner, vm, vm.warpDB, warpSignatureCacheSize) // clear warpdb on initialization if config enabled if vm.config.PruneWarpDB { @@ -936,9 +935,7 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]http.Handler, error) { if vm.config.WarpAPIEnabled { validatorsState := warpValidators.NewState(vm.ctx) - signatureGetter := &aggregator.NetworkSignatureGetter{Client: vm.client} - warpAggregator := aggregator.New(vm.ctx.SubnetID, validatorsState, signatureGetter) - if err := handler.RegisterName("warp", warp.NewAPI(vm.warpBackend, warpAggregator)); err != nil { + if err := handler.RegisterName("warp", warp.NewAPI(vm.ctx.NetworkID, vm.ctx.SubnetID, vm.ctx.ChainID, validatorsState, vm.warpBackend, vm.client)); err != nil { return nil, err } enabledAPIs = append(enabledAPIs, "warp") diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index 2914eeb2b3..2c71ebac15 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -3273,7 +3273,7 @@ func TestCrossChainMessagestoVM(t *testing.T) { require.True(calledSendCrossChainAppResponseFn, "sendCrossChainAppResponseFn was not called") } -func TestSignatureRequestsToVM(t *testing.T) { +func TestMessageSignatureRequestsToVM(t *testing.T) { _, vm, _, appSender := GenesisVM(t, true, genesisJSONSubnetEVM, "", "") defer func() { @@ -3288,7 +3288,7 @@ func TestSignatureRequestsToVM(t *testing.T) { // Add the known message and get its signature to confirm. err = vm.warpBackend.AddMessage(warpMessage) require.NoError(t, err) - signature, err := vm.warpBackend.GetSignature(warpMessage.ID()) + signature, err := vm.warpBackend.GetMessageSignature(warpMessage.ID()) require.NoError(t, err) tests := map[string]struct { @@ -3317,7 +3317,7 @@ func TestSignatureRequestsToVM(t *testing.T) { return nil } t.Run(name, func(t *testing.T) { - var signatureRequest message.Request = message.SignatureRequest{ + var signatureRequest message.Request = message.MessageSignatureRequest{ MessageID: test.messageID, } @@ -3332,3 +3332,59 @@ func TestSignatureRequestsToVM(t *testing.T) { }) } } + +func TestBlockSignatureRequestsToVM(t *testing.T) { + _, vm, _, appSender := GenesisVM(t, true, genesisJSONSubnetEVM, "", "") + + defer func() { + err := vm.Shutdown(context.Background()) + require.NoError(t, err) + }() + + lastAcceptedID, err := vm.LastAccepted(context.Background()) + require.NoError(t, err) + + signature, err := vm.warpBackend.GetBlockSignature(lastAcceptedID) + require.NoError(t, err) + + tests := map[string]struct { + blockID ids.ID + expectedResponse [bls.SignatureLen]byte + }{ + "known": { + blockID: lastAcceptedID, + expectedResponse: signature, + }, + "unknown": { + blockID: ids.GenerateTestID(), + expectedResponse: [bls.SignatureLen]byte{}, + }, + } + + for name, test := range tests { + calledSendAppResponseFn := false + appSender.SendAppResponseF = func(ctx context.Context, nodeID ids.NodeID, requestID uint32, responseBytes []byte) error { + calledSendAppResponseFn = true + var response message.SignatureResponse + _, err := message.Codec.Unmarshal(responseBytes, &response) + require.NoError(t, err) + require.Equal(t, test.expectedResponse, response.Signature) + + return nil + } + t.Run(name, func(t *testing.T) { + var signatureRequest message.Request = message.BlockSignatureRequest{ + BlockID: test.blockID, + } + + requestBytes, err := message.Codec.Marshal(message.Version, &signatureRequest) + require.NoError(t, err) + + // Send the app request and make sure we called SendAppResponseFn + deadline := time.Now().Add(60 * time.Second) + err = vm.Network.AppRequest(context.Background(), ids.GenerateTestNodeID(), 1, deadline, requestBytes) + require.NoError(t, err) + require.True(t, calledSendAppResponseFn) + }) + } +} diff --git a/plugin/evm/vm_warp_test.go b/plugin/evm/vm_warp_test.go index 0d0745b984..4e1321a8d5 100644 --- a/plugin/evm/vm_warp_test.go +++ b/plugin/evm/vm_warp_test.go @@ -113,13 +113,17 @@ func TestSendWarpMessage(t *testing.T) { unsignedMessageID := unsignedMessage.ID() // Verify the signature cannot be fetched before the block is accepted - _, err = vm.warpBackend.GetSignature(unsignedMessageID) + _, err = vm.warpBackend.GetMessageSignature(unsignedMessageID) + require.Error(err) + _, err = vm.warpBackend.GetBlockSignature(blk.ID()) require.Error(err) require.NoError(vm.SetPreference(context.Background(), blk.ID())) require.NoError(blk.Accept(context.Background())) vm.blockChain.DrainAcceptorQueue() - rawSignatureBytes, err := vm.warpBackend.GetSignature(unsignedMessageID) + + // Verify the message signature after accepting the block. + rawSignatureBytes, err := vm.warpBackend.GetMessageSignature(unsignedMessageID) require.NoError(err) blsSignature, err := bls.SignatureFromBytes(rawSignatureBytes[:]) require.NoError(err) @@ -132,7 +136,21 @@ func TestSendWarpMessage(t *testing.T) { require.Fail("Failed to read accepted logs from subscription") } - // Verify the produced signature is valid + // Verify the produced message signature is valid + require.True(bls.Verify(vm.ctx.PublicKey, blsSignature, unsignedMessage.Bytes())) + + // Verify the blockID will now be signed by the backend and produces a valid signature. + rawSignatureBytes, err = vm.warpBackend.GetBlockSignature(blk.ID()) + require.NoError(err) + blsSignature, err = bls.SignatureFromBytes(rawSignatureBytes[:]) + require.NoError(err) + + blockHashPayload, err := payload.NewHash(blk.ID()) + require.NoError(err) + unsignedMessage, err = avalancheWarp.NewUnsignedMessage(vm.ctx.NetworkID, vm.ctx.ChainID, blockHashPayload.Bytes()) + require.NoError(err) + + // Verify the produced message signature is valid require.True(bls.Verify(vm.ctx.PublicKey, blsSignature, unsignedMessage.Bytes())) } diff --git a/tests/warp/warp_test.go b/tests/warp/warp_test.go index 2311bcd974..23d2a821ad 100644 --- a/tests/warp/warp_test.go +++ b/tests/warp/warp_test.go @@ -18,8 +18,7 @@ import ( "github.com/ava-labs/avalanche-network-runner/rpcpb" "github.com/ava-labs/avalanchego/api/info" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/crypto/bls" - "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/vms/platformvm" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/subnet-evm/core/types" @@ -31,6 +30,7 @@ import ( "github.com/ava-labs/subnet-evm/tests/utils" "github.com/ava-labs/subnet-evm/tests/utils/runner" warpBackend "github.com/ava-labs/subnet-evm/warp" + "github.com/ava-labs/subnet-evm/warp/aggregator" "github.com/ava-labs/subnet-evm/x/warp" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -45,10 +45,16 @@ var ( config = runner.NewDefaultANRConfig() manager = runner.NewNetworkManager(config) warpChainConfigPath string + networkID uint32 unsignedWarpMsg *avalancheWarp.UnsignedMessage unsignedWarpMessageID ids.ID signedWarpMsg *avalancheWarp.Message + warpBlockID ids.ID + warpBlockHashPayload *payload.Hash + warpBlockHashUnsignedMsg *avalancheWarp.UnsignedMessage + warpBlockHashSignedMsg *avalancheWarp.Message blockchainIDA, blockchainIDB ids.ID + subnetIDA, subnetIDB ids.ID chainAURIs, chainBURIs []string chainAWSClient, chainBWSClient ethclient.Client chainID = big.NewInt(99999) @@ -56,6 +62,7 @@ var ( fundedAddress common.Address testPayload = []byte{1, 2, 3} txSigner = types.LatestSignerForChainID(chainID) + nodesPerSubnet = 5 ) func TestE2E(t *testing.T) { @@ -76,15 +83,12 @@ var _ = ginkgo.BeforeSuite(func() { var err error // Name 10 new validators (which should have BLS key registered) subnetANodeNames := make([]string, 0) - subnetBNodeNames := []string{} - for i := 1; i <= 10; i++ { - n := fmt.Sprintf("node%d-bls", i) - if i <= 5 { - subnetANodeNames = append(subnetANodeNames, n) - } else { - subnetBNodeNames = append(subnetBNodeNames, n) - } + subnetBNodeNames := make([]string, 0) + for i := 0; i < nodesPerSubnet; i++ { + subnetANodeNames = append(subnetANodeNames, fmt.Sprintf("node%d-subnetA-bls", i)) + subnetBNodeNames = append(subnetBNodeNames, fmt.Sprintf("node%d-subnetB-bls", i)) } + f, err := os.CreateTemp(os.TempDir(), "config.json") gomega.Expect(err).Should(gomega.BeNil()) _, err = f.Write([]byte(`{"warp-api-enabled": true}`)) @@ -144,17 +148,23 @@ var _ = ginkgo.BeforeSuite(func() { subnetADetails, ok := manager.GetSubnet(subnetA) gomega.Expect(ok).Should(gomega.BeTrue()) blockchainIDA = subnetADetails.BlockchainID - gomega.Expect(len(subnetADetails.ValidatorURIs)).Should(gomega.Equal(5)) + subnetIDA = subnetADetails.SubnetID + gomega.Expect(len(subnetADetails.ValidatorURIs)).Should(gomega.Equal(nodesPerSubnet)) chainAURIs = append(chainAURIs, subnetADetails.ValidatorURIs...) + infoClient := info.NewClient(chainAURIs[0]) + networkID, err = infoClient.GetNetworkID(context.Background()) + gomega.Expect(err).Should(gomega.BeNil()) + subnetB = subnetIDs[1] subnetBDetails, ok = manager.GetSubnet(subnetB) gomega.Expect(ok).Should(gomega.BeTrue()) blockchainIDB = subnetBDetails.BlockchainID - gomega.Expect(len(subnetBDetails.ValidatorURIs)).Should(gomega.Equal(5)) + subnetIDB = subnetBDetails.SubnetID + gomega.Expect(len(subnetBDetails.ValidatorURIs)).Should(gomega.Equal(nodesPerSubnet)) chainBURIs = append(chainBURIs, subnetBDetails.ValidatorURIs...) - log.Info("Created URIs for both subnets", "ChainAURIs", chainAURIs, "ChainBURIs", chainBURIs, "blockchainIDA", blockchainIDA, "blockchainIDB", blockchainIDB) + log.Info("Created URIs for both subnets", "ChainAURIs", chainAURIs, "ChainBURIs", chainBURIs, "blockchainIDA", blockchainIDA, "subnetIDA", subnetIDA, "blockchainIDB", blockchainIDB, "subnetIDB", subnetIDB) chainAWSURI := toWebsocketURI(chainAURIs[0], blockchainIDA.String()) log.Info("Creating ethclient for blockchainA", "wsURI", chainAWSURI) @@ -210,6 +220,13 @@ var _ = ginkgo.Describe("[Warp]", ginkgo.Ordered, func() { newHead := <-newHeads blockHash := newHead.Hash() + log.Info("Constructing warp block hash unsigned message", "blockHash", blockHash) + warpBlockID = ids.ID(blockHash) // Set warpBlockID to construct a warp message containing a block hash payload later + warpBlockHashPayload, err = payload.NewHash(warpBlockID) + gomega.Expect(err).Should(gomega.BeNil()) + warpBlockHashUnsignedMsg, err = avalancheWarp.NewUnsignedMessage(networkID, blockchainIDA, warpBlockHashPayload.Bytes()) + gomega.Expect(err).Should(gomega.BeNil()) + log.Info("Fetching relevant warp logs from the newly produced block") logs, err := chainAWSClient.FilterLogs(ctx, interfaces.FilterQuery{ BlockHash: &blockHash, @@ -256,53 +273,55 @@ var _ = ginkgo.Describe("[Warp]", ginkgo.Ordered, func() { ginkgo.It("Aggregate Warp Signature via API", ginkgo.Label("Warp", "ReceiveWarp", "AggregateWarpManually"), func() { ctx := context.Background() - blsSignatures := make([]*bls.Signature, 0, len(chainAURIs)) - for i, uri := range chainAURIs { + warpAPIs := make(map[ids.NodeID]warpBackend.Client, len(chainAURIs)) + for _, uri := range chainAURIs { client, err := warpBackend.NewClient(uri, blockchainIDA.String()) gomega.Expect(err).Should(gomega.BeNil()) - log.Info("Fetching warp signature from node") - rawSignatureBytes, err := client.GetSignature(ctx, unsignedWarpMessageID) - gomega.Expect(err).Should(gomega.BeNil()) - - blsSignature, err := bls.SignatureFromBytes(rawSignatureBytes) - gomega.Expect(err).Should(gomega.BeNil()) infoClient := info.NewClient(uri) - nodeID, blsSigner, err := infoClient.GetNodeID(ctx) + nodeID, _, err := infoClient.GetNodeID(ctx) gomega.Expect(err).Should(gomega.BeNil()) - - blsSignatures = append(blsSignatures, blsSignature) - - blsPublicKey := blsSigner.Key() - log.Info("Verifying BLS Signature from node", "nodeID", nodeID, "nodeIndex", i) - gomega.Expect(bls.Verify(blsPublicKey, blsSignature, unsignedWarpMsg.Bytes())).Should(gomega.BeTrue()) + warpAPIs[nodeID] = client } - blsAggregatedSignature, err := bls.AggregateSignatures(blsSignatures) + pChainClient := platformvm.NewClient(chainAURIs[0]) + pChainHeight, err := pChainClient.GetHeight(ctx) gomega.Expect(err).Should(gomega.BeNil()) - - signersBitSet := set.NewBits() - for i := 0; i < len(blsSignatures); i++ { - signersBitSet.Add(i) - } - warpSignature := &avalancheWarp.BitSetSignature{ - Signers: signersBitSet.Bytes(), + validators, err := pChainClient.GetValidatorsAt(ctx, subnetIDA, pChainHeight) + gomega.Expect(err).Should(gomega.BeNil()) + gomega.Expect(len(validators)).Should(gomega.Equal(nodesPerSubnet)) + totalWeight := uint64(0) + warpValidators := make([]*avalancheWarp.Validator, 0, len(validators)) + for nodeID, validator := range validators { + warpValidators = append(warpValidators, &avalancheWarp.Validator{ + PublicKey: validator.PublicKey, + Weight: validator.Weight, + NodeIDs: []ids.NodeID{nodeID}, + }) + totalWeight += validator.Weight } - blsAggregatedSignatureBytes := bls.SignatureToBytes(blsAggregatedSignature) - copy(warpSignature.Signature[:], blsAggregatedSignatureBytes) + log.Info("Aggregating signatures from validator set", "numValidators", len(warpValidators), "totalWeight", totalWeight) + apiSignatureGetter := warpBackend.NewAPIFetcher(warpAPIs) + signatureResult, err := aggregator.New(apiSignatureGetter, warpValidators, totalWeight).AggregateSignatures(ctx, unsignedWarpMsg, 100) + gomega.Expect(err).Should(gomega.BeNil()) + gomega.Expect(signatureResult.SignatureWeight).Should(gomega.Equal(signatureResult.TotalWeight)) + gomega.Expect(signatureResult.SignatureWeight).Should(gomega.Equal(totalWeight)) - warpMsg, err := avalancheWarp.NewMessage( - unsignedWarpMsg, - warpSignature, - ) + signedWarpMsg = signatureResult.Message + + signatureResult, err = aggregator.New(apiSignatureGetter, warpValidators, totalWeight).AggregateSignatures(ctx, warpBlockHashUnsignedMsg, 100) gomega.Expect(err).Should(gomega.BeNil()) - signedWarpMsg = warpMsg + gomega.Expect(signatureResult.SignatureWeight).Should(gomega.Equal(signatureResult.TotalWeight)) + gomega.Expect(signatureResult.SignatureWeight).Should(gomega.Equal(totalWeight)) + warpBlockHashSignedMsg = signatureResult.Message + + log.Info("Aggregated signatures for warp messages", "signedWarpMsg", common.Bytes2Hex(signedWarpMsg.Bytes()), "warpBlockHashSignedMsg", common.Bytes2Hex(warpBlockHashSignedMsg.Bytes())) }) - // Aggregate a Warp Signature using the node's Signature Aggregation API call and verifying that its output matches the + // Aggregate a Warp Message Signature using the node's Signature Aggregation API call and verifying that its output matches the // the manual construction - ginkgo.It("Aggregate Warp Signature via Aggregator", ginkgo.Label("Warp", "ReceiveWarp", "AggregatorWarp"), func() { + ginkgo.It("Aggregate Warp Message Signature via Aggregator", ginkgo.Label("Warp", "ReceiveWarp", "AggregatorWarp"), func() { ctx := context.Background() // Verify that the signature aggregation matches the results of manually constructing the warp message @@ -310,11 +329,26 @@ var _ = ginkgo.Describe("[Warp]", ginkgo.Ordered, func() { gomega.Expect(err).Should(gomega.BeNil()) // Specify WarpQuorumDenominator to retrieve signatures from every validator - signedWarpMessageBytes, err := client.GetAggregateSignature(ctx, unsignedWarpMessageID, params.WarpQuorumDenominator) + signedWarpMessageBytes, err := client.GetMessageAggregateSignature(ctx, unsignedWarpMessageID, params.WarpQuorumDenominator) gomega.Expect(err).Should(gomega.BeNil()) gomega.Expect(signedWarpMessageBytes).Should(gomega.Equal(signedWarpMsg.Bytes())) }) + // Aggregate a Warp Block Signature using the node's Signature Aggregation API call and verifying that its output matches the + // the manual construction + ginkgo.It("Aggregate Warp Block Signature via Aggregator", ginkgo.Label("Warp", "ReceiveWarp", "AggregatorWarp"), func() { + ctx := context.Background() + + // Verify that the signature aggregation matches the results of manually constructing the warp message + client, err := warpBackend.NewClient(chainAURIs[0], blockchainIDA.String()) + gomega.Expect(err).Should(gomega.BeNil()) + + // Specify WarpQuorumDenominator to retrieve signatures from every validator + signedWarpBlockBytes, err := client.GetBlockAggregateSignature(ctx, warpBlockID, params.WarpQuorumDenominator) + gomega.Expect(err).Should(gomega.BeNil()) + gomega.Expect(signedWarpBlockBytes).Should(gomega.Equal(warpBlockHashSignedMsg.Bytes())) + }) + // Verify successful delivery of the Avalanche Warp Message from Chain A to Chain B ginkgo.It("Verify Message from A to B", ginkgo.Label("Warp", "VerifyMessage"), func() { ctx := context.Background() @@ -366,6 +400,57 @@ var _ = ginkgo.Describe("[Warp]", ginkgo.Ordered, func() { gomega.Expect(receipt.Status).Should(gomega.Equal(types.ReceiptStatusSuccessful)) }) + // Verify successful delivery of the Avalanche Warp Block Hash from Chain A to Chain B + ginkgo.It("Verify Block Hash from A to B", ginkgo.Label("Warp", "VerifyMessage"), func() { + ctx := context.Background() + + log.Info("Subscribing to new heads") + newHeads := make(chan *types.Header, 10) + sub, err := chainBWSClient.SubscribeNewHead(ctx, newHeads) + gomega.Expect(err).Should(gomega.BeNil()) + defer sub.Unsubscribe() + + nonce, err := chainBWSClient.NonceAt(ctx, fundedAddress, nil) + gomega.Expect(err).Should(gomega.BeNil()) + + packedInput, err := warp.PackGetVerifiedWarpBlockHash(0) + gomega.Expect(err).Should(gomega.BeNil()) + tx := predicate.NewPredicateTx( + chainID, + nonce, + &warp.Module.Address, + 5_000_000, + big.NewInt(225*params.GWei), + big.NewInt(params.GWei), + common.Big0, + packedInput, + types.AccessList{}, + warp.ContractAddress, + warpBlockHashSignedMsg.Bytes(), + ) + signedTx, err := types.SignTx(tx, txSigner, fundedKey) + gomega.Expect(err).Should(gomega.BeNil()) + txBytes, err := signedTx.MarshalBinary() + gomega.Expect(err).Should(gomega.BeNil()) + log.Info("Sending getVerifiedWarpBlockHash transaction", "txHash", signedTx.Hash(), "txBytes", common.Bytes2Hex(txBytes)) + err = chainBWSClient.SendTransaction(ctx, signedTx) + gomega.Expect(err).Should(gomega.BeNil()) + + log.Info("Waiting for new block confirmation") + newHead := <-newHeads + blockHash := newHead.Hash() + log.Info("Fetching relevant warp logs and receipts from new block") + logs, err := chainBWSClient.FilterLogs(ctx, interfaces.FilterQuery{ + BlockHash: &blockHash, + Addresses: []common.Address{warp.Module.Address}, + }) + gomega.Expect(err).Should(gomega.BeNil()) + gomega.Expect(len(logs)).Should(gomega.Equal(0)) + receipt, err := chainBWSClient.TransactionReceipt(ctx, signedTx.Hash()) + gomega.Expect(err).Should(gomega.BeNil()) + gomega.Expect(receipt.Status).Should(gomega.Equal(types.ReceiptStatusSuccessful)) + }) + ginkgo.It("Send Message from A to B from Hardhat", ginkgo.Label("Warp", "IWarpMessenger", "SendWarpMessage"), func() { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() diff --git a/warp/aggregator/aggregator.go b/warp/aggregator/aggregator.go index d5c98fb50f..7f53075c55 100644 --- a/warp/aggregator/aggregator.go +++ b/warp/aggregator/aggregator.go @@ -5,22 +5,17 @@ package aggregator import ( "context" - "errors" "fmt" "github.com/ava-labs/subnet-evm/params" "github.com/ethereum/go-ethereum/log" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/set" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" ) -var errNoValidators = errors.New("cannot aggregate signatures from subnet with no validators") - type AggregateSignatureResult struct { // Weight of validators included in the aggregate signature. SignatureWeight uint64 @@ -30,63 +25,40 @@ type AggregateSignatureResult struct { Message *avalancheWarp.Message } +type signatureFetchResult struct { + sig *bls.Signature + index int + weight uint64 +} + // Aggregator requests signatures from validators and // aggregates them into a single signature. type Aggregator struct { - // Aggregating signatures for a chain validated by this subnet. - subnetID ids.ID - // Fetches signatures from validators. - client SignatureGetter - // Validator state for this chain. - state validators.State + validators []*avalancheWarp.Validator + totalWeight uint64 + client SignatureGetter } // New returns a signature aggregator for the chain with the given [state] on the // given [subnetID], and where [client] can be used to fetch signatures from validators. -func New(subnetID ids.ID, state validators.State, client SignatureGetter) *Aggregator { +func New(client SignatureGetter, validators []*avalancheWarp.Validator, totalWeight uint64) *Aggregator { return &Aggregator{ - subnetID: subnetID, - client: client, - state: state, + client: client, + validators: validators, + totalWeight: totalWeight, } } // Returns an aggregate signature over [unsignedMessage]. // The returned signature's weight exceeds the threshold given by [quorumNum]. func (a *Aggregator) AggregateSignatures(ctx context.Context, unsignedMessage *avalancheWarp.UnsignedMessage, quorumNum uint64) (*AggregateSignatureResult, error) { - // Note: we use the current height as a best guess of the canonical validator set when the aggregated signature will be verified - // by the recipient chain. If the validator set changes from [pChainHeight] to the P-Chain height that is actually specified by the - // ProposerVM header when this message is verified, then the aggregate signature could become outdated and require re-aggregation. - pChainHeight, err := a.state.GetCurrentHeight(ctx) - if err != nil { - return nil, err - } - - log.Debug("Fetching signature", - "a.subnetID", a.subnetID, - "height", pChainHeight, - ) - validators, totalWeight, err := avalancheWarp.GetCanonicalValidatorSet(ctx, a.state, pChainHeight, a.subnetID) - if err != nil { - return nil, fmt.Errorf("failed to get validator set: %w", err) - } - if len(validators) == 0 { - return nil, fmt.Errorf("%w (SubnetID: %s, Height: %d)", errNoValidators, a.subnetID, pChainHeight) - } - - type signatureFetchResult struct { - sig *bls.Signature - index int - weight uint64 - } - // Create a child context to cancel signature fetching if we reach signature threshold. signatureFetchCtx, signatureFetchCancel := context.WithCancel(ctx) defer signatureFetchCancel() // Fetch signatures from validators concurrently. signatureFetchResultChan := make(chan *signatureFetchResult) - for i, validator := range validators { + for i, validator := range a.validators { var ( i = i validator = validator @@ -137,13 +109,13 @@ func (a *Aggregator) AggregateSignatures(ctx context.Context, unsignedMessage *a } var ( - signatures = make([]*bls.Signature, 0, len(validators)) + signatures = make([]*bls.Signature, 0, len(a.validators)) signersBitset = set.NewBits() signaturesWeight = uint64(0) signaturesPassedThreshold = false ) - for i := 0; i < len(validators); i++ { + for i := 0; i < len(a.validators); i++ { signatureFetchResult := <-signatureFetchResultChan if signatureFetchResult == nil { continue @@ -159,10 +131,10 @@ func (a *Aggregator) AggregateSignatures(ctx context.Context, unsignedMessage *a ) // If the signature weight meets the requested threshold, cancel signature fetching - if err := avalancheWarp.VerifyWeight(signaturesWeight, totalWeight, quorumNum, params.WarpQuorumDenominator); err == nil { + if err := avalancheWarp.VerifyWeight(signaturesWeight, a.totalWeight, quorumNum, params.WarpQuorumDenominator); err == nil { log.Debug("Verify weight passed, exiting aggregation early", "quorumNum", quorumNum, - "totalWeight", totalWeight, + "totalWeight", a.totalWeight, "signatureWeight", signaturesWeight, "msgID", unsignedMessage.ID(), ) @@ -196,6 +168,6 @@ func (a *Aggregator) AggregateSignatures(ctx context.Context, unsignedMessage *a return &AggregateSignatureResult{ Message: msg, SignatureWeight: signaturesWeight, - TotalWeight: totalWeight, + TotalWeight: a.totalWeight, }, nil } diff --git a/warp/aggregator/aggregator_test.go b/warp/aggregator/aggregator_test.go index 6668eba766..07d2020756 100644 --- a/warp/aggregator/aggregator_test.go +++ b/warp/aggregator/aggregator_test.go @@ -13,7 +13,6 @@ import ( "go.uber.org/mock/gomock" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/crypto/bls" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" ) @@ -31,9 +30,7 @@ func newValidator(t testing.TB, weight uint64) (*bls.SecretKey, *avalancheWarp.V } func TestAggregateSignatures(t *testing.T) { - subnetID := ids.GenerateTestID() errTest := errors.New("test error") - pChainHeight := uint64(1337) unsignedMsg := &avalancheWarp.UnsignedMessage{ NetworkID: 1338, SourceChainID: ids.ID{'y', 'e', 'e', 't'}, @@ -57,20 +54,20 @@ func TestAggregateSignatures(t *testing.T) { nonVdrSk, err := bls.NewSecretKey() require.NoError(t, err) nonVdrSig := bls.Sign(nonVdrSk, unsignedMsg.Bytes()) - vdrSet := map[ids.NodeID]*validators.GetValidatorOutput{ - nodeID1: { - NodeID: nodeID1, + vdrs := []*avalancheWarp.Validator{ + { PublicKey: vdr1.PublicKey, + NodeIDs: []ids.NodeID{nodeID1}, Weight: vdr1.Weight, }, - nodeID2: { - NodeID: nodeID2, + { PublicKey: vdr2.PublicKey, + NodeIDs: []ids.NodeID{nodeID2}, Weight: vdr2.Weight, }, - nodeID3: { - NodeID: nodeID3, + { PublicKey: vdr3.PublicKey, + NodeIDs: []ids.NodeID{nodeID3}, Weight: vdr3.Weight, }, } @@ -86,64 +83,15 @@ func TestAggregateSignatures(t *testing.T) { } tests := []test{ - { - name: "can't get height", - contextWithCancelFunc: func() (context.Context, context.CancelFunc) { - return context.Background(), nil - }, - aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { - state := validators.NewMockState(ctrl) - state.EXPECT().GetCurrentHeight(gomock.Any()).Return(uint64(0), errTest) - return New(subnetID, state, nil) - }, - unsignedMsg: nil, - quorumNum: 0, - expectedErr: errTest, - }, - { - name: "can't get validator set", - contextWithCancelFunc: func() (context.Context, context.CancelFunc) { - return context.Background(), nil - }, - aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { - state := validators.NewMockState(ctrl) - state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) - state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errTest) - return New(subnetID, state, nil) - }, - unsignedMsg: nil, - expectedErr: errTest, - }, - { - name: "no validators exist", - contextWithCancelFunc: func() (context.Context, context.CancelFunc) { - return context.Background(), nil - }, - aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { - state := validators.NewMockState(ctrl) - state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) - state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil) - return New(subnetID, state, nil) - }, - unsignedMsg: nil, - quorumNum: 0, - expectedErr: errNoValidators, - }, { name: "0/3 validators reply with signature", contextWithCancelFunc: func() (context.Context, context.CancelFunc) { return context.Background(), nil }, aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { - state := validators.NewMockState(ctrl) - state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) - state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( - vdrSet, nil, - ) - client := NewMockSignatureGetter(ctrl) - client.EXPECT().GetSignature(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errTest).Times(len(vdrSet)) - return New(subnetID, state, client) + client.EXPECT().GetSignature(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errTest).Times(len(vdrs)) + return New(client, vdrs, vdrWeight*uint64(len(vdrs))) }, unsignedMsg: unsignedMsg, quorumNum: 1, @@ -155,17 +103,11 @@ func TestAggregateSignatures(t *testing.T) { return context.Background(), nil }, aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { - state := validators.NewMockState(ctrl) - state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) - state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( - vdrSet, nil, - ) - client := NewMockSignatureGetter(ctrl) client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(sig1, nil).Times(1) client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(nil, errTest).Times(1) client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(nil, errTest).Times(1) - return New(subnetID, state, client) + return New(client, vdrs, vdrWeight*uint64(len(vdrs))) }, unsignedMsg: unsignedMsg, quorumNum: 35, // Require >1/3 of weight @@ -177,17 +119,11 @@ func TestAggregateSignatures(t *testing.T) { return context.Background(), nil }, aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { - state := validators.NewMockState(ctrl) - state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) - state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( - vdrSet, nil, - ) - client := NewMockSignatureGetter(ctrl) client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(sig1, nil).Times(1) client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(sig2, nil).Times(1) client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(nil, errTest).Times(1) - return New(subnetID, state, client) + return New(client, vdrs, vdrWeight*uint64(len(vdrs))) }, unsignedMsg: unsignedMsg, quorumNum: 69, // Require >2/3 of weight @@ -199,17 +135,11 @@ func TestAggregateSignatures(t *testing.T) { return context.Background(), nil }, aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { - state := validators.NewMockState(ctrl) - state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) - state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( - vdrSet, nil, - ) - client := NewMockSignatureGetter(ctrl) client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(sig1, nil).Times(1) client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(sig2, nil).Times(1) client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(nil, errTest).MaxTimes(1) - return New(subnetID, state, client) + return New(client, vdrs, vdrWeight*uint64(len(vdrs))) }, unsignedMsg: unsignedMsg, quorumNum: 65, // Require <2/3 of weight @@ -222,17 +152,11 @@ func TestAggregateSignatures(t *testing.T) { return context.Background(), nil }, aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { - state := validators.NewMockState(ctrl) - state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) - state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( - vdrSet, nil, - ) - client := NewMockSignatureGetter(ctrl) client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(sig1, nil).MaxTimes(1) client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(sig2, nil).MaxTimes(1) client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(sig3, nil).MaxTimes(1) - return New(subnetID, state, client) + return New(client, vdrs, vdrWeight*uint64(len(vdrs))) }, unsignedMsg: unsignedMsg, quorumNum: 100, // Require all weight @@ -245,17 +169,11 @@ func TestAggregateSignatures(t *testing.T) { return context.Background(), nil }, aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { - state := validators.NewMockState(ctrl) - state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) - state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( - vdrSet, nil, - ) - client := NewMockSignatureGetter(ctrl) client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(nonVdrSig, nil).MaxTimes(1) client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(sig2, nil).Times(1) client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(sig3, nil).Times(1) - return New(subnetID, state, client) + return New(client, vdrs, vdrWeight*uint64(len(vdrs))) }, unsignedMsg: unsignedMsg, quorumNum: 64, @@ -268,17 +186,11 @@ func TestAggregateSignatures(t *testing.T) { return context.Background(), nil }, aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { - state := validators.NewMockState(ctrl) - state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) - state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( - vdrSet, nil, - ) - client := NewMockSignatureGetter(ctrl) client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(nonVdrSig, nil).Times(1) client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(nonVdrSig, nil).Times(1) client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(nonVdrSig, nil).Times(1) - return New(subnetID, state, client) + return New(client, vdrs, vdrWeight*uint64(len(vdrs))) }, unsignedMsg: unsignedMsg, quorumNum: 1, @@ -290,17 +202,11 @@ func TestAggregateSignatures(t *testing.T) { return context.Background(), nil }, aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { - state := validators.NewMockState(ctrl) - state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) - state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( - vdrSet, nil, - ) - client := NewMockSignatureGetter(ctrl) client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(nonVdrSig, nil).Times(1) client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(nonVdrSig, nil).Times(1) client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(sig3, nil).Times(1) - return New(subnetID, state, client) + return New(client, vdrs, vdrWeight*uint64(len(vdrs))) }, unsignedMsg: unsignedMsg, quorumNum: 40, @@ -312,17 +218,11 @@ func TestAggregateSignatures(t *testing.T) { return context.Background(), nil }, aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { - state := validators.NewMockState(ctrl) - state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) - state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( - vdrSet, nil, - ) - client := NewMockSignatureGetter(ctrl) client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(nonVdrSig, nil).MaxTimes(1) client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(nil, errTest).MaxTimes(1) client.EXPECT().GetSignature(gomock.Any(), nodeID3, gomock.Any()).Return(sig3, nil).Times(1) - return New(subnetID, state, client) + return New(client, vdrs, vdrWeight*uint64(len(vdrs))) }, unsignedMsg: unsignedMsg, quorumNum: 30, @@ -337,12 +237,6 @@ func TestAggregateSignatures(t *testing.T) { return ctx, cancel }, aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { - state := validators.NewMockState(ctrl) - state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) - state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( - vdrSet, nil, - ) - // Assert that the context passed into each goroutine is canceled // because the parent context is canceled. client := NewMockSignatureGetter(ctrl) @@ -370,7 +264,7 @@ func TestAggregateSignatures(t *testing.T) { return nil, err }, ).MaxTimes(1) - return New(subnetID, state, client) + return New(client, vdrs, vdrWeight*uint64(len(vdrs))) }, unsignedMsg: unsignedMsg, quorumNum: 60, // Require 2/3 validators @@ -384,12 +278,6 @@ func TestAggregateSignatures(t *testing.T) { return ctx, cancel }, aggregatorFunc: func(ctrl *gomock.Controller, cancel context.CancelFunc) *Aggregator { - state := validators.NewMockState(ctrl) - state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) - state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( - vdrSet, nil, - ) - client := NewMockSignatureGetter(ctrl) client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).DoAndReturn( func(ctx context.Context, _ ids.NodeID, _ *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { @@ -416,7 +304,7 @@ func TestAggregateSignatures(t *testing.T) { return nil, err }, ).MaxTimes(1) - return New(subnetID, state, client) + return New(client, vdrs, vdrWeight*uint64(len(vdrs))) }, unsignedMsg: unsignedMsg, quorumNum: 33, // 1/3 Should have gotten one signature before cancellation @@ -429,12 +317,6 @@ func TestAggregateSignatures(t *testing.T) { return context.Background(), nil }, aggregatorFunc: func(ctrl *gomock.Controller, _ context.CancelFunc) *Aggregator { - state := validators.NewMockState(ctrl) - state.EXPECT().GetCurrentHeight(gomock.Any()).Return(pChainHeight, nil) - state.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), gomock.Any()).Return( - vdrSet, nil, - ) - client := NewMockSignatureGetter(ctrl) client.EXPECT().GetSignature(gomock.Any(), nodeID1, gomock.Any()).Return(sig1, nil).Times(1) client.EXPECT().GetSignature(gomock.Any(), nodeID2, gomock.Any()).Return(sig2, nil).Times(1) @@ -448,7 +330,7 @@ func TestAggregateSignatures(t *testing.T) { return nil, err }, ).MaxTimes(1) - return New(subnetID, state, client) + return New(client, vdrs, vdrWeight*uint64(len(vdrs))) }, unsignedMsg: unsignedMsg, quorumNum: 60, // Require 2/3 validators diff --git a/warp/aggregator/signature_getter.go b/warp/aggregator/signature_getter.go index 526280bc9b..4fdb2219e0 100644 --- a/warp/aggregator/signature_getter.go +++ b/warp/aggregator/signature_getter.go @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "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" ) @@ -38,17 +39,39 @@ type NetworkSignatureGetter struct { Client NetworkClient } +func NewSignatureGetter(client NetworkClient) *NetworkSignatureGetter { + return &NetworkSignatureGetter{ + Client: client, + } +} + // GetSignature attempts to fetch a BLS Signature of [unsignedWarpMessage] from [nodeID] until it succeeds or receives an invalid response // // Note: this function will continue attempting to fetch the signature from [nodeID] until it receives an invalid value or [ctx] is cancelled. // The caller is responsible to cancel [ctx] if it no longer needs to fetch this signature. func (s *NetworkSignatureGetter) GetSignature(ctx context.Context, nodeID ids.NodeID, unsignedWarpMessage *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { - signatureReq := message.SignatureRequest{ - MessageID: unsignedWarpMessage.ID(), - } - signatureReqBytes, err := message.RequestToBytes(message.Codec, signatureReq) + var signatureReqBytes []byte + parsedPayload, err := payload.Parse(unsignedWarpMessage.Payload) if err != nil { - return nil, fmt.Errorf("failed to marshal signature request: %w", err) + return nil, fmt.Errorf("failed to parse unsigned message payload: %w", err) + } + switch p := parsedPayload.(type) { + case *payload.AddressedCall: + signatureReq := message.MessageSignatureRequest{ + MessageID: unsignedWarpMessage.ID(), + } + signatureReqBytes, err = message.RequestToBytes(message.Codec, signatureReq) + if err != nil { + return nil, fmt.Errorf("failed to marshal signature request: %w", err) + } + case *payload.Hash: + signatureReq := message.BlockSignatureRequest{ + BlockID: p.Hash, + } + signatureReqBytes, err = message.RequestToBytes(message.Codec, signatureReq) + if err != nil { + return nil, fmt.Errorf("failed to marshal signature request: %w", err) + } } delay := initialRetryFetchSignatureDelay diff --git a/warp/backend.go b/warp/backend.go index fac81248c1..e40d21c426 100644 --- a/warp/backend.go +++ b/warp/backend.go @@ -4,13 +4,17 @@ 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/choices" + "github.com/ava-labs/avalanchego/snow/consensus/snowman" "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/ethdb" "github.com/ethereum/go-ethereum/log" ) @@ -19,14 +23,21 @@ var _ Backend = &backend{} const batchSize = ethdb.IdealBatchSize +type BlockClient interface { + GetBlock(ctx context.Context, blockID ids.ID) (snowman.Block, error) +} + // Backend 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 Backend interface { // AddMessage signs [unsignedMessage] and adds it to the warp backend database AddMessage(unsignedMessage *avalancheWarp.UnsignedMessage) error - // GetSignature returns the signature of the requested message hash. - GetSignature(messageHash ids.ID) ([bls.SignatureLen]byte, error) + // GetMessageSignature returns the signature of the requested message hash. + GetMessageSignature(messageID ids.ID) ([bls.SignatureLen]byte, error) + + // GetBlockSignature returns the signature of the requested message hash. + GetBlockSignature(blockID ids.ID) ([bls.SignatureLen]byte, error) // GetMessage retrieves the [unsignedMessage] from the warp backend database if available GetMessage(messageHash ids.ID) (*avalancheWarp.UnsignedMessage, error) @@ -37,24 +48,33 @@ type Backend interface { // backend implements Backend, keeps track of warp messages, and generates message signatures. type backend struct { - db database.Database - warpSigner avalancheWarp.Signer - signatureCache *cache.LRU[ids.ID, [bls.SignatureLen]byte] - messageCache *cache.LRU[ids.ID, *avalancheWarp.UnsignedMessage] + networkID uint32 + sourceChainID ids.ID + db database.Database + warpSigner avalancheWarp.Signer + blockClient BlockClient + messageSignatureCache *cache.LRU[ids.ID, [bls.SignatureLen]byte] + blockSignatureCache *cache.LRU[ids.ID, [bls.SignatureLen]byte] + messageCache *cache.LRU[ids.ID, *avalancheWarp.UnsignedMessage] } // NewBackend creates a new Backend, and initializes the signature cache and message tracking database. -func NewBackend(warpSigner avalancheWarp.Signer, db database.Database, cacheSize int) Backend { +func NewBackend(networkID uint32, sourceChainID ids.ID, warpSigner avalancheWarp.Signer, blockClient BlockClient, db database.Database, cacheSize int) Backend { return &backend{ - db: db, - warpSigner: warpSigner, - signatureCache: &cache.LRU[ids.ID, [bls.SignatureLen]byte]{Size: cacheSize}, - messageCache: &cache.LRU[ids.ID, *avalancheWarp.UnsignedMessage]{Size: cacheSize}, + networkID: networkID, + sourceChainID: sourceChainID, + db: db, + warpSigner: warpSigner, + blockClient: blockClient, + messageSignatureCache: &cache.LRU[ids.ID, [bls.SignatureLen]byte]{Size: cacheSize}, + blockSignatureCache: &cache.LRU[ids.ID, [bls.SignatureLen]byte]{Size: cacheSize}, + messageCache: &cache.LRU[ids.ID, *avalancheWarp.UnsignedMessage]{Size: cacheSize}, } } func (b *backend) Clear() error { - b.signatureCache.Flush() + b.messageSignatureCache.Flush() + b.blockSignatureCache.Flush() b.messageCache.Flush() return database.Clear(b.db, batchSize) } @@ -76,14 +96,14 @@ func (b *backend) AddMessage(unsignedMessage *avalancheWarp.UnsignedMessage) err } copy(signature[:], sig) - b.signatureCache.Put(messageID, signature) + b.messageSignatureCache.Put(messageID, signature) log.Debug("Adding warp message to backend", "messageID", messageID) return nil } -func (b *backend) GetSignature(messageID ids.ID) ([bls.SignatureLen]byte, error) { +func (b *backend) GetMessageSignature(messageID ids.ID) ([bls.SignatureLen]byte, error) { log.Debug("Getting warp message from backend", "messageID", messageID) - if sig, ok := b.signatureCache.Get(messageID); ok { + if sig, ok := b.messageSignatureCache.Get(messageID); ok { return sig, nil } @@ -99,7 +119,40 @@ func (b *backend) GetSignature(messageID ids.ID) ([bls.SignatureLen]byte, error) } copy(signature[:], sig) - b.signatureCache.Put(messageID, signature) + b.messageSignatureCache.Put(messageID, signature) + return signature, nil +} + +func (b *backend) GetBlockSignature(blockID ids.ID) ([bls.SignatureLen]byte, error) { + log.Debug("Getting block from backend", "blockID", blockID) + if sig, ok := b.blockSignatureCache.Get(blockID); ok { + return sig, nil + } + + block, err := b.blockClient.GetBlock(context.TODO(), blockID) + if err != nil { + return [bls.SignatureLen]byte{}, fmt.Errorf("failed to get block %s: %w", blockID, err) + } + if block.Status() != choices.Accepted { + return [bls.SignatureLen]byte{}, fmt.Errorf("block %s was not accepted", blockID) + } + + var signature [bls.SignatureLen]byte + blockHashPayload, err := payload.NewHash(blockID) + if err != nil { + return [bls.SignatureLen]byte{}, fmt.Errorf("failed to create new block hash payload: %w", err) + } + unsignedMessage, err := avalancheWarp.NewUnsignedMessage(b.networkID, b.sourceChainID, blockHashPayload.Bytes()) + if err != nil { + return [bls.SignatureLen]byte{}, fmt.Errorf("failed to create new unsigned warp message: %w", err) + } + sig, err := b.warpSigner.Sign(unsignedMessage) + if err != nil { + return [bls.SignatureLen]byte{}, fmt.Errorf("failed to sign warp message: %w", err) + } + + copy(signature[:], sig) + b.blockSignatureCache.Put(blockID, signature) return signature, nil } diff --git a/warp/backend_test.go b/warp/backend_test.go index 9f92234b44..024375a308 100644 --- a/warp/backend_test.go +++ b/warp/backend_test.go @@ -4,20 +4,27 @@ package warp import ( + "context" + "errors" "testing" "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow/choices" + "github.com/ava-labs/avalanchego/snow/consensus/snowman" + "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/snow/engine/snowman/block" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/hashing" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/stretchr/testify/require" ) var ( networkID uint32 = 54321 sourceChainID = ids.GenerateTestID() - payload = []byte("test") + testPayload = []byte("test") ) func TestClearDB(t *testing.T) { @@ -26,7 +33,7 @@ func TestClearDB(t *testing.T) { sk, err := bls.NewSecretKey() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID) - backendIntf := NewBackend(warpSigner, db, 500) + backendIntf := NewBackend(networkID, sourceChainID, warpSigner, nil, db, 500) backend, ok := backendIntf.(*backend) require.True(t, ok) @@ -43,21 +50,22 @@ func TestClearDB(t *testing.T) { err = backend.AddMessage(unsignedMsg) require.NoError(t, err) // ensure that the message was added - _, err = backend.GetSignature(messageID) + _, err = backend.GetMessageSignature(messageID) require.NoError(t, err) } err = backend.Clear() require.NoError(t, err) require.Zero(t, backend.messageCache.Len()) - require.Zero(t, backend.signatureCache.Len()) + require.Zero(t, backend.messageSignatureCache.Len()) + require.Zero(t, backend.blockSignatureCache.Len()) it := db.NewIterator() defer it.Release() require.False(t, it.Next()) // ensure all messages have been deleted for _, messageID := range messageIDs { - _, err := backend.GetSignature(messageID) + _, err := backend.GetMessageSignature(messageID) require.ErrorContains(t, err, "failed to get warp message") } } @@ -68,17 +76,17 @@ func TestAddAndGetValidMessage(t *testing.T) { sk, err := bls.NewSecretKey() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID) - backend := NewBackend(warpSigner, db, 500) + backend := NewBackend(networkID, sourceChainID, warpSigner, nil, db, 500) // Create a new unsigned message and add it to the warp backend. - unsignedMsg, err := avalancheWarp.NewUnsignedMessage(networkID, sourceChainID, payload) + unsignedMsg, err := avalancheWarp.NewUnsignedMessage(networkID, sourceChainID, testPayload) require.NoError(t, err) err = backend.AddMessage(unsignedMsg) require.NoError(t, err) // Verify that a signature is returned successfully, and compare to expected signature. messageID := unsignedMsg.ID() - signature, err := backend.GetSignature(messageID) + signature, err := backend.GetMessageSignature(messageID) require.NoError(t, err) expectedSig, err := warpSigner.Sign(unsignedMsg) @@ -92,16 +100,56 @@ func TestAddAndGetUnknownMessage(t *testing.T) { sk, err := bls.NewSecretKey() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID) - backend := NewBackend(warpSigner, db, 500) - unsignedMsg, err := avalancheWarp.NewUnsignedMessage(networkID, sourceChainID, payload) + backend := NewBackend(networkID, sourceChainID, warpSigner, nil, db, 500) + unsignedMsg, err := avalancheWarp.NewUnsignedMessage(networkID, sourceChainID, testPayload) require.NoError(t, err) // Try getting a signature for a message that was not added. messageID := unsignedMsg.ID() - _, err = backend.GetSignature(messageID) + _, err = backend.GetMessageSignature(messageID) require.Error(t, err) } +func TestGetBlockSignature(t *testing.T) { + require := require.New(t) + + blkID := ids.GenerateTestID() + testVM := &block.TestVM{ + TestVM: common.TestVM{T: t}, + GetBlockF: func(ctx context.Context, i ids.ID) (snowman.Block, error) { + if i == blkID { + return &snowman.TestBlock{ + TestDecidable: choices.TestDecidable{ + IDV: blkID, + StatusV: choices.Accepted, + }, + }, nil + } + return nil, errors.New("invalid blockID") + }, + } + db := memdb.New() + + sk, err := bls.NewSecretKey() + require.NoError(err) + warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID) + backend := NewBackend(networkID, sourceChainID, warpSigner, testVM, db, 500) + + blockHashPayload, err := payload.NewHash(blkID) + require.NoError(err) + unsignedMessage, err := avalancheWarp.NewUnsignedMessage(networkID, sourceChainID, blockHashPayload.Bytes()) + require.NoError(err) + expectedSig, err := warpSigner.Sign(unsignedMessage) + require.NoError(err) + + signature, err := backend.GetBlockSignature(blkID) + require.NoError(err) + require.Equal(expectedSig, signature[:]) + + _, err = backend.GetBlockSignature(ids.GenerateTestID()) + require.Error(err) +} + func TestZeroSizedCache(t *testing.T) { db := memdb.New() @@ -110,17 +158,17 @@ func TestZeroSizedCache(t *testing.T) { warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID) // Verify zero sized cache works normally, because the lru cache will be initialized to size 1 for any size parameter <= 0. - backend := NewBackend(warpSigner, db, 0) + backend := NewBackend(networkID, sourceChainID, warpSigner, nil, db, 0) // Create a new unsigned message and add it to the warp backend. - unsignedMsg, err := avalancheWarp.NewUnsignedMessage(networkID, sourceChainID, payload) + unsignedMsg, err := avalancheWarp.NewUnsignedMessage(networkID, sourceChainID, testPayload) require.NoError(t, err) err = backend.AddMessage(unsignedMsg) require.NoError(t, err) // Verify that a signature is returned successfully, and compare to expected signature. messageID := unsignedMsg.ID() - signature, err := backend.GetSignature(messageID) + signature, err := backend.GetMessageSignature(messageID) require.NoError(t, err) expectedSig, err := warpSigner.Sign(unsignedMsg) diff --git a/warp/client.go b/warp/client.go index c9e116db56..7e23c7e9a1 100644 --- a/warp/client.go +++ b/warp/client.go @@ -15,10 +15,10 @@ import ( var _ Client = (*client)(nil) type Client interface { - // GetSignature requests the BLS signature associated with a messageID - GetSignature(ctx context.Context, messageID ids.ID) ([]byte, error) - // GetAggregateSignature requests the aggregate signature associated with messageID - GetAggregateSignature(ctx context.Context, messageID ids.ID, quorumNum uint64) ([]byte, error) + GetMessageSignature(ctx context.Context, messageID ids.ID) ([]byte, error) + GetMessageAggregateSignature(ctx context.Context, messageID ids.ID, quorumNum uint64) ([]byte, error) + GetBlockSignature(ctx context.Context, blockID ids.ID) ([]byte, error) + GetBlockAggregateSignature(ctx context.Context, blockID ids.ID, quorumNum uint64) ([]byte, error) } // client implementation for interacting with EVM [chain] @@ -37,18 +37,34 @@ func NewClient(uri, chain string) (Client, error) { }, nil } -func (c *client) GetSignature(ctx context.Context, messageID ids.ID) ([]byte, error) { +func (c *client) GetMessageSignature(ctx context.Context, messageID ids.ID) ([]byte, error) { var res hexutil.Bytes - if err := c.client.CallContext(ctx, &res, "warp_getSignature", messageID); err != nil { - return nil, fmt.Errorf("call to warp_getSignature failed. err: %w", err) + if err := c.client.CallContext(ctx, &res, "warp_getMessageSignature", messageID); err != nil { + return nil, fmt.Errorf("call to warp_getMessageSignature failed. err: %w", err) } return res, nil } -func (c *client) GetAggregateSignature(ctx context.Context, messageID ids.ID, quorumNum uint64) ([]byte, error) { +func (c *client) GetMessageAggregateSignature(ctx context.Context, messageID ids.ID, quorumNum uint64) ([]byte, error) { var res hexutil.Bytes - if err := c.client.CallContext(ctx, &res, "warp_getAggregateSignature", messageID, quorumNum); err != nil { - return nil, fmt.Errorf("call to warp_getAggregateSignature failed. err: %w", err) + if err := c.client.CallContext(ctx, &res, "warp_getMessageAggregateSignature", messageID, quorumNum); err != nil { + return nil, fmt.Errorf("call to warp_getMessageAggregateSignature failed. err: %w", err) + } + return res, nil +} + +func (c *client) GetBlockSignature(ctx context.Context, blockID ids.ID) ([]byte, error) { + var res hexutil.Bytes + if err := c.client.CallContext(ctx, &res, "warp_getBlockSignature", blockID); err != nil { + return nil, fmt.Errorf("call to warp_getBlockSignature failed. err: %w", err) + } + return res, nil +} + +func (c *client) GetBlockAggregateSignature(ctx context.Context, blockID ids.ID, quorumNum uint64) ([]byte, error) { + var res hexutil.Bytes + if err := c.client.CallContext(ctx, &res, "warp_getBlockAggregateSignature", blockID, quorumNum); err != nil { + return nil, fmt.Errorf("call to warp_getBlockAggregateSignature failed. err: %w", err) } return res, nil } diff --git a/warp/fetcher.go b/warp/fetcher.go index eba0bc7949..fcf014650b 100644 --- a/warp/fetcher.go +++ b/warp/fetcher.go @@ -10,8 +10,12 @@ import ( "github.com/ava-labs/avalanchego/ids" "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/warp/aggregator" ) +var _ aggregator.SignatureGetter = (*apiFetcher)(nil) + type apiFetcher struct { clients map[ids.NodeID]Client } @@ -22,13 +26,22 @@ func NewAPIFetcher(clients map[ids.NodeID]Client) *apiFetcher { } } -func (f *apiFetcher) FetchWarpSignature(ctx context.Context, nodeID ids.NodeID, unsignedWarpMessage *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { +func (f *apiFetcher) GetSignature(ctx context.Context, nodeID ids.NodeID, unsignedWarpMessage *avalancheWarp.UnsignedMessage) (*bls.Signature, error) { client, ok := f.clients[nodeID] if !ok { return nil, fmt.Errorf("no warp client for nodeID: %s", nodeID) } - - signatureBytes, err := client.GetSignature(ctx, unsignedWarpMessage.ID()) + var signatureBytes []byte + parsedPayload, err := payload.Parse(unsignedWarpMessage.Payload) + if err != nil { + return nil, fmt.Errorf("failed to parse unsigned message payload: %w", err) + } + switch p := parsedPayload.(type) { + case *payload.AddressedCall: + signatureBytes, err = client.GetMessageSignature(ctx, unsignedWarpMessage.ID()) + case *payload.Hash: + signatureBytes, err = client.GetBlockSignature(ctx, p.Hash) + } if err != nil { return nil, err } diff --git a/warp/handlers/signature_request.go b/warp/handlers/signature_request.go index 30be346327..c307d284d1 100644 --- a/warp/handlers/signature_request.go +++ b/warp/handlers/signature_request.go @@ -15,7 +15,7 @@ import ( "github.com/ethereum/go-ethereum/log" ) -// SignatureRequestHandler serves warp signature requests. It is a peer.RequestHandler for message.SignatureRequest. +// SignatureRequestHandler serves warp signature requests. It is a peer.RequestHandler for message.MessageSignatureRequest. type SignatureRequestHandler struct { backend warp.Backend codec codec.Manager @@ -30,27 +30,55 @@ func NewSignatureRequestHandler(backend warp.Backend, codec codec.Manager) *Sign } } -// OnSignatureRequest handles message.SignatureRequest, and retrieves a warp signature for the requested message ID. +// OnMessageSignatureRequest handles message.MessageSignatureRequest, and retrieves a warp signature for the requested message ID. // Never returns an error // Expects returned errors to be treated as FATAL // Returns empty response if signature is not found // Assumes ctx is active -func (s *SignatureRequestHandler) OnSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest message.SignatureRequest) ([]byte, error) { +func (s *SignatureRequestHandler) OnMessageSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest message.MessageSignatureRequest) ([]byte, error) { startTime := time.Now() - s.stats.IncSignatureRequest() + s.stats.IncMessageSignatureRequest() // Always report signature request time defer func() { - s.stats.UpdateSignatureRequestTime(time.Since(startTime)) + s.stats.UpdateMessageSignatureRequestTime(time.Since(startTime)) }() - signature, err := s.backend.GetSignature(signatureRequest.MessageID) + signature, err := s.backend.GetMessageSignature(signatureRequest.MessageID) if err != nil { log.Debug("Unknown warp signature requested", "messageID", signatureRequest.MessageID) - s.stats.IncSignatureMiss() + s.stats.IncMessageSignatureMiss() signature = [bls.SignatureLen]byte{} } else { - s.stats.IncSignatureHit() + s.stats.IncMessageSignatureHit() + } + + response := message.SignatureResponse{Signature: signature} + responseBytes, err := s.codec.Marshal(message.Version, &response) + if err != nil { + log.Error("could not marshal SignatureResponse, dropping request", "nodeID", nodeID, "requestID", requestID, "err", err) + return nil, nil + } + + return responseBytes, nil +} + +func (s *SignatureRequestHandler) OnBlockSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, request message.BlockSignatureRequest) ([]byte, error) { + startTime := time.Now() + s.stats.IncBlockSignatureRequest() + + // Always report signature request time + defer func() { + s.stats.UpdateBlockSignatureRequestTime(time.Since(startTime)) + }() + + signature, err := s.backend.GetBlockSignature(request.BlockID) + if err != nil { + log.Debug("Unknown warp signature requested", "blockID", request.BlockID) + s.stats.IncBlockSignatureMiss() + signature = [bls.SignatureLen]byte{} + } else { + s.stats.IncBlockSignatureHit() } response := message.SignatureResponse{Signature: signature} @@ -65,6 +93,10 @@ func (s *SignatureRequestHandler) OnSignatureRequest(ctx context.Context, nodeID type NoopSignatureRequestHandler struct{} -func (s *NoopSignatureRequestHandler) OnSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest message.SignatureRequest) ([]byte, error) { +func (s *NoopSignatureRequestHandler) OnMessageSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest message.MessageSignatureRequest) ([]byte, error) { + return nil, nil +} + +func (s *NoopSignatureRequestHandler) OnBlockSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest message.BlockSignatureRequest) ([]byte, error) { return nil, nil } diff --git a/warp/handlers/signature_request_test.go b/warp/handlers/signature_request_test.go index 0d4a1fb373..7a672bde8e 100644 --- a/warp/handlers/signature_request_test.go +++ b/warp/handlers/signature_request_test.go @@ -5,12 +5,17 @@ package handlers import ( "context" + "errors" "testing" "time" "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/snow/choices" + "github.com/ava-labs/avalanchego/snow/consensus/snowman" + "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/snow/engine/snowman/block" "github.com/ava-labs/avalanchego/utils/crypto/bls" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/subnet-evm/plugin/evm/message" @@ -18,54 +23,62 @@ import ( "github.com/stretchr/testify/require" ) -func TestSignatureHandler(t *testing.T) { +func TestMessageSignatureHandler(t *testing.T) { database := memdb.New() snowCtx := snow.DefaultContextTest() blsSecretKey, err := bls.NewSecretKey() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(blsSecretKey, snowCtx.NetworkID, snowCtx.ChainID) - backend := warp.NewBackend(warpSigner, database, 100) + backend := warp.NewBackend(snowCtx.NetworkID, snowCtx.ChainID, warpSigner, &block.TestVM{TestVM: common.TestVM{T: t}}, database, 100) msg, err := avalancheWarp.NewUnsignedMessage(snowCtx.NetworkID, snowCtx.ChainID, []byte("test")) require.NoError(t, err) messageID := msg.ID() require.NoError(t, backend.AddMessage(msg)) - signature, err := backend.GetSignature(messageID) + signature, err := backend.GetMessageSignature(messageID) require.NoError(t, err) unknownMessageID := ids.GenerateTestID() emptySignature := [bls.SignatureLen]byte{} tests := map[string]struct { - setup func() (request message.SignatureRequest, expectedResponse []byte) + setup func() (request message.MessageSignatureRequest, expectedResponse []byte) verifyStats func(t *testing.T, stats *handlerStats) }{ - "normal": { - setup: func() (request message.SignatureRequest, expectedResponse []byte) { - return message.SignatureRequest{ + "known message": { + setup: func() (request message.MessageSignatureRequest, expectedResponse []byte) { + return message.MessageSignatureRequest{ MessageID: messageID, }, signature[:] }, verifyStats: func(t *testing.T, stats *handlerStats) { - require.EqualValues(t, 1, stats.signatureRequest.Count()) - require.EqualValues(t, 1, stats.signatureHit.Count()) - require.EqualValues(t, 0, stats.signatureMiss.Count()) - require.Greater(t, stats.signatureRequestDuration.Value(), time.Duration(0)) + require.EqualValues(t, 1, stats.messageSignatureRequest.Count()) + require.EqualValues(t, 1, stats.messageSignatureHit.Count()) + require.EqualValues(t, 0, stats.messageSignatureMiss.Count()) + require.Greater(t, stats.messageSignatureRequestDuration.Value(), time.Duration(0)) + require.EqualValues(t, 0, stats.blockSignatureRequest.Count()) + require.EqualValues(t, 0, stats.blockSignatureHit.Count()) + require.EqualValues(t, 0, stats.blockSignatureMiss.Count()) + require.EqualValues(t, stats.blockSignatureRequestDuration.Value(), time.Duration(0)) }, }, - "unknown": { - setup: func() (request message.SignatureRequest, expectedResponse []byte) { - return message.SignatureRequest{ + "unknown message": { + setup: func() (request message.MessageSignatureRequest, expectedResponse []byte) { + return message.MessageSignatureRequest{ MessageID: unknownMessageID, }, emptySignature[:] }, verifyStats: func(t *testing.T, stats *handlerStats) { - require.EqualValues(t, 1, stats.signatureRequest.Count()) - require.EqualValues(t, 0, stats.signatureHit.Count()) - require.EqualValues(t, 1, stats.signatureMiss.Count()) - require.Greater(t, stats.signatureRequestDuration.Value(), time.Duration(0)) + require.EqualValues(t, 1, stats.messageSignatureRequest.Count()) + require.EqualValues(t, 0, stats.messageSignatureHit.Count()) + require.EqualValues(t, 1, stats.messageSignatureMiss.Count()) + require.Greater(t, stats.messageSignatureRequestDuration.Value(), time.Duration(0)) + require.EqualValues(t, 0, stats.blockSignatureRequest.Count()) + require.EqualValues(t, 0, stats.blockSignatureHit.Count()) + require.EqualValues(t, 0, stats.blockSignatureMiss.Count()) + require.EqualValues(t, stats.blockSignatureRequestDuration.Value(), time.Duration(0)) }, }, } @@ -76,7 +89,109 @@ func TestSignatureHandler(t *testing.T) { handler.stats.Clear() request, expectedResponse := test.setup() - responseBytes, err := handler.OnSignatureRequest(context.Background(), ids.GenerateTestNodeID(), 1, request) + responseBytes, err := handler.OnMessageSignatureRequest(context.Background(), ids.GenerateTestNodeID(), 1, request) + require.NoError(t, err) + + test.verifyStats(t, handler.stats) + + // If the expected response is empty, assert that the handler returns an empty response and return early. + if len(expectedResponse) == 0 { + require.Len(t, responseBytes, 0, "expected response to be empty") + return + } + var response message.SignatureResponse + _, err = message.Codec.Unmarshal(responseBytes, &response) + require.NoError(t, err, "error unmarshalling SignatureResponse") + + require.Equal(t, expectedResponse, response.Signature[:]) + }) + } +} + +func TestBlockSignatureHandler(t *testing.T) { + database := memdb.New() + snowCtx := snow.DefaultContextTest() + blsSecretKey, err := bls.NewSecretKey() + require.NoError(t, err) + + warpSigner := avalancheWarp.NewSigner(blsSecretKey, snowCtx.NetworkID, snowCtx.ChainID) + blkID := ids.GenerateTestID() + testVM := &block.TestVM{ + TestVM: common.TestVM{T: t}, + GetBlockF: func(ctx context.Context, i ids.ID) (snowman.Block, error) { + if i == blkID { + return &snowman.TestBlock{ + TestDecidable: choices.TestDecidable{ + IDV: blkID, + StatusV: choices.Accepted, + }, + }, nil + } + return nil, errors.New("invalid blockID") + }, + } + backend := warp.NewBackend( + snowCtx.NetworkID, + snowCtx.ChainID, + warpSigner, + testVM, + database, + 100, + ) + + signature, err := backend.GetBlockSignature(blkID) + require.NoError(t, err) + unknownMessageID := ids.GenerateTestID() + + emptySignature := [bls.SignatureLen]byte{} + + tests := map[string]struct { + setup func() (request message.BlockSignatureRequest, expectedResponse []byte) + verifyStats func(t *testing.T, stats *handlerStats) + }{ + "known block": { + setup: func() (request message.BlockSignatureRequest, expectedResponse []byte) { + return message.BlockSignatureRequest{ + BlockID: blkID, + }, signature[:] + }, + verifyStats: func(t *testing.T, stats *handlerStats) { + require.EqualValues(t, 0, stats.messageSignatureRequest.Count()) + require.EqualValues(t, 0, stats.messageSignatureHit.Count()) + require.EqualValues(t, 0, stats.messageSignatureMiss.Count()) + require.EqualValues(t, stats.messageSignatureRequestDuration.Value(), time.Duration(0)) + require.EqualValues(t, 1, stats.blockSignatureRequest.Count()) + require.EqualValues(t, 1, stats.blockSignatureHit.Count()) + require.EqualValues(t, 0, stats.blockSignatureMiss.Count()) + require.Greater(t, stats.blockSignatureRequestDuration.Value(), time.Duration(0)) + }, + }, + "unknown block": { + setup: func() (request message.BlockSignatureRequest, expectedResponse []byte) { + return message.BlockSignatureRequest{ + BlockID: unknownMessageID, + }, emptySignature[:] + }, + verifyStats: func(t *testing.T, stats *handlerStats) { + require.EqualValues(t, 0, stats.messageSignatureRequest.Count()) + require.EqualValues(t, 0, stats.messageSignatureHit.Count()) + require.EqualValues(t, 0, stats.messageSignatureMiss.Count()) + require.EqualValues(t, stats.messageSignatureRequestDuration.Value(), time.Duration(0)) + require.EqualValues(t, 1, stats.blockSignatureRequest.Count()) + require.EqualValues(t, 0, stats.blockSignatureHit.Count()) + require.EqualValues(t, 1, stats.blockSignatureMiss.Count()) + require.Greater(t, stats.blockSignatureRequestDuration.Value(), time.Duration(0)) + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + handler := NewSignatureRequestHandler(backend, message.Codec) + handler.stats.Clear() + + request, expectedResponse := test.setup() + responseBytes, err := handler.OnBlockSignatureRequest(context.Background(), ids.GenerateTestNodeID(), 1, request) require.NoError(t, err) test.verifyStats(t, handler.stats) diff --git a/warp/handlers/stats.go b/warp/handlers/stats.go index dfb261f9a3..481f2aaac0 100644 --- a/warp/handlers/stats.go +++ b/warp/handlers/stats.go @@ -10,31 +10,50 @@ import ( ) type handlerStats struct { - // SignatureRequestHandler metrics - signatureRequest metrics.Counter - signatureHit metrics.Counter - signatureMiss metrics.Counter - signatureRequestDuration metrics.Gauge + // MessageSignatureRequestHandler metrics + messageSignatureRequest metrics.Counter + messageSignatureHit metrics.Counter + messageSignatureMiss metrics.Counter + messageSignatureRequestDuration metrics.Gauge + // BlockSignatureRequestHandler metrics + blockSignatureRequest metrics.Counter + blockSignatureHit metrics.Counter + blockSignatureMiss metrics.Counter + blockSignatureRequestDuration metrics.Gauge } func newStats() *handlerStats { return &handlerStats{ - signatureRequest: metrics.GetOrRegisterCounter("signature_request_count", nil), - signatureHit: metrics.GetOrRegisterCounter("signature_request_hit", nil), - signatureMiss: metrics.GetOrRegisterCounter("signature_request_miss", nil), - signatureRequestDuration: metrics.GetOrRegisterGauge("signature_request_duration", nil), + messageSignatureRequest: metrics.GetOrRegisterCounter("message_signature_request_count", nil), + messageSignatureHit: metrics.GetOrRegisterCounter("message_signature_request_hit", nil), + messageSignatureMiss: metrics.GetOrRegisterCounter("message_signature_request_miss", nil), + messageSignatureRequestDuration: metrics.GetOrRegisterGauge("message_signature_request_duration", nil), + blockSignatureRequest: metrics.GetOrRegisterCounter("block_signature_request_count", nil), + blockSignatureHit: metrics.GetOrRegisterCounter("block_signature_request_hit", nil), + blockSignatureMiss: metrics.GetOrRegisterCounter("block_signature_request_miss", nil), + blockSignatureRequestDuration: metrics.GetOrRegisterGauge("block_signature_request_duration", nil), } } -func (h *handlerStats) IncSignatureRequest() { h.signatureRequest.Inc(1) } -func (h *handlerStats) IncSignatureHit() { h.signatureHit.Inc(1) } -func (h *handlerStats) IncSignatureMiss() { h.signatureMiss.Inc(1) } -func (h *handlerStats) UpdateSignatureRequestTime(duration time.Duration) { - h.signatureRequestDuration.Inc(int64(duration)) +func (h *handlerStats) IncMessageSignatureRequest() { h.messageSignatureRequest.Inc(1) } +func (h *handlerStats) IncMessageSignatureHit() { h.messageSignatureHit.Inc(1) } +func (h *handlerStats) IncMessageSignatureMiss() { h.messageSignatureMiss.Inc(1) } +func (h *handlerStats) UpdateMessageSignatureRequestTime(duration time.Duration) { + h.messageSignatureRequestDuration.Inc(int64(duration)) +} +func (h *handlerStats) IncBlockSignatureRequest() { h.blockSignatureRequest.Inc(1) } +func (h *handlerStats) IncBlockSignatureHit() { h.blockSignatureHit.Inc(1) } +func (h *handlerStats) IncBlockSignatureMiss() { h.blockSignatureMiss.Inc(1) } +func (h *handlerStats) UpdateBlockSignatureRequestTime(duration time.Duration) { + h.blockSignatureRequestDuration.Inc(int64(duration)) } func (h *handlerStats) Clear() { - h.signatureRequest.Clear() - h.signatureHit.Clear() - h.signatureMiss.Clear() - h.signatureRequestDuration.Update(0) + h.messageSignatureRequest.Clear() + h.messageSignatureHit.Clear() + h.messageSignatureMiss.Clear() + h.messageSignatureRequestDuration.Update(0) + h.blockSignatureRequest.Clear() + h.blockSignatureHit.Clear() + h.blockSignatureMiss.Clear() + h.blockSignatureRequestDuration.Update(0) } diff --git a/warp/service.go b/warp/service.go index ee907ead30..a3e632a470 100644 --- a/warp/service.go +++ b/warp/service.go @@ -5,43 +5,102 @@ package warp import ( "context" + "errors" "fmt" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" + "github.com/ava-labs/subnet-evm/peer" "github.com/ava-labs/subnet-evm/warp/aggregator" + "github.com/ava-labs/subnet-evm/warp/validators" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/log" ) +var errNoValidators = errors.New("cannot aggregate signatures from subnet with no validators") + // API introduces snowman specific functionality to the evm type API struct { - backend Backend - aggregator *aggregator.Aggregator + networkID uint32 + sourceSubnetID, sourceChainID ids.ID + backend Backend + state *validators.State + client peer.NetworkClient } -func NewAPI(backend Backend, aggregator *aggregator.Aggregator) *API { +func NewAPI(networkID uint32, sourceSubnetID ids.ID, sourceChainID ids.ID, state *validators.State, backend Backend, client peer.NetworkClient) *API { return &API{ - backend: backend, - aggregator: aggregator, + networkID: networkID, + sourceSubnetID: sourceSubnetID, + sourceChainID: sourceChainID, + backend: backend, + state: state, + client: client, + } +} + +// GetMessageSignature returns the BLS signature associated with a messageID. +func (a *API) GetMessageSignature(ctx context.Context, messageID ids.ID) (hexutil.Bytes, error) { + signature, err := a.backend.GetMessageSignature(messageID) + if err != nil { + return nil, fmt.Errorf("failed to get signature for message %s with error %w", messageID, err) } + return signature[:], nil } -// GetSignature returns the BLS signature associated with a messageID. -func (a *API) GetSignature(ctx context.Context, messageID ids.ID) (hexutil.Bytes, error) { - signature, err := a.backend.GetSignature(messageID) +// GetBlockSignature returns the BLS signature associated with a blockID. +func (a *API) GetBlockSignature(ctx context.Context, blockID ids.ID) (hexutil.Bytes, error) { + signature, err := a.backend.GetBlockSignature(blockID) if err != nil { - return nil, fmt.Errorf("failed to get signature for with error %w", err) + return nil, fmt.Errorf("failed to get signature for block %s with error %w", blockID, err) } return signature[:], nil } -// GetAggregateSignature fetches the aggregate signature for the requested [messageID] -func (a *API) GetAggregateSignature(ctx context.Context, messageID ids.ID, quorumNum uint64) (signedMessageBytes hexutil.Bytes, err error) { +// GetMessageAggregateSignature fetches the aggregate signature for the requested [messageID] +func (a *API) GetMessageAggregateSignature(ctx context.Context, messageID ids.ID, quorumNum uint64) (signedMessageBytes hexutil.Bytes, err error) { unsignedMessage, err := a.backend.GetMessage(messageID) if err != nil { return nil, err } + return a.aggregateSignatures(ctx, unsignedMessage, quorumNum) +} + +// GetBlockAggregateSignature fetches the aggregate signature for the requested [blockID] +func (a *API) GetBlockAggregateSignature(ctx context.Context, blockID ids.ID, quorumNum uint64) (signedMessageBytes hexutil.Bytes, err error) { + blockHashPayload, err := payload.NewHash(blockID) + if err != nil { + return nil, err + } + unsignedMessage, err := warp.NewUnsignedMessage(a.networkID, a.sourceChainID, blockHashPayload.Bytes()) + if err != nil { + return nil, err + } + + return a.aggregateSignatures(ctx, unsignedMessage, quorumNum) +} + +func (a *API) aggregateSignatures(ctx context.Context, unsignedMessage *warp.UnsignedMessage, quorumNum uint64) (hexutil.Bytes, error) { + pChainHeight, err := a.state.GetCurrentHeight(ctx) + if err != nil { + return nil, err + } + + log.Debug("Fetching signature", + "a.subnetID", a.sourceSubnetID, + "height", pChainHeight, + ) + validators, totalWeight, err := warp.GetCanonicalValidatorSet(ctx, a.state, pChainHeight, a.sourceSubnetID) + if err != nil { + return nil, fmt.Errorf("failed to get validator set: %w", err) + } + if len(validators) == 0 { + return nil, fmt.Errorf("%w (SubnetID: %s, Height: %d)", errNoValidators, a.sourceSubnetID, pChainHeight) + } - signatureResult, err := a.aggregator.AggregateSignatures(ctx, unsignedMessage, quorumNum) + agg := aggregator.New(aggregator.NewSignatureGetter(a.client), validators, totalWeight) + signatureResult, err := agg.AggregateSignatures(ctx, unsignedMessage, quorumNum) if err != nil { return nil, err } From 9af512513fde06d037218169baee9bd54d9ce625 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Thu, 26 Oct 2023 05:07:03 -0400 Subject: [PATCH 51/52] predicate: fix predicate bytes incorrect error log (#969) * predicate: fix predicate bytes incorrect error log * predicate: add unit test for GetPredicateResultBytes --- core/evm.go | 4 ++-- plugin/evm/block.go | 6 +++--- predicate/predicate_bytes.go | 12 ++++++------ predicate/predicate_bytes_test.go | 16 ++++++++++++++++ 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/core/evm.go b/core/evm.go index 035d1c9ed3..d13a9907ce 100644 --- a/core/evm.go +++ b/core/evm.go @@ -50,8 +50,8 @@ type ChainContext interface { // NewEVMBlockContext creates a new context for use in the EVM. func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext { - predicateBytes, err := predicate.GetPredicateResultBytes(header.Extra) - if err != nil { + predicateBytes, ok := predicate.GetPredicateResultBytes(header.Extra) + if !ok { return newEVMBlockContext(header, chain, author, nil) } // Prior to the DUpgrade, the VM enforces the extra data is smaller than or diff --git a/plugin/evm/block.go b/plugin/evm/block.go index 9dc3b1eb23..681d44e067 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -271,9 +271,9 @@ func (b *Block) verifyPredicates(predicateContext *precompileconfig.PredicateCon return fmt.Errorf("failed to marshal predicate results: %w", err) } extraData := b.ethBlock.Extra() - headerPredicateResultsBytes, err := predicate.GetPredicateResultBytes(extraData) - if err != nil { - return err + headerPredicateResultsBytes, ok := predicate.GetPredicateResultBytes(extraData) + if !ok { + return fmt.Errorf("failed to find predicate results in extra data: %x", extraData) } if !bytes.Equal(headerPredicateResultsBytes, predicateResultsBytes) { return fmt.Errorf("%w (remote: %x local: %x)", errInvalidHeaderPredicateResults, headerPredicateResultsBytes, predicateResultsBytes) diff --git a/predicate/predicate_bytes.go b/predicate/predicate_bytes.go index d86e624d85..627adb2bb8 100644 --- a/predicate/predicate_bytes.go +++ b/predicate/predicate_bytes.go @@ -51,14 +51,14 @@ func UnpackPredicate(paddedPredicate []byte) ([]byte, error) { return trimmedPredicateBytes[:len(trimmedPredicateBytes)-1], nil } -// GetPredicateResultBytes returns the predicate result bytes from the extra data. -// If the extra data does not contain predicate result bytes, an error is returned. -func GetPredicateResultBytes(extraData []byte) ([]byte, error) { +// GetPredicateResultBytes returns the predicate result bytes from the extra data and +// true iff the predicate results bytes have non-zero length. +func GetPredicateResultBytes(extraData []byte) ([]byte, bool) { // Prior to the DUpgrade, the VM enforces the extra data is smaller than or equal to this size. // After the DUpgrade, the VM pre-verifies the extra data past the dynamic fee rollup window is // valid. - if len(extraData) < params.DynamicFeeExtraDataSize { - return nil, fmt.Errorf("%w: got: %d, required: %d", ErrorInvalidExtraData, len(extraData), params.DynamicFeeExtraDataSize) + if len(extraData) <= params.DynamicFeeExtraDataSize { + return nil, false } - return extraData[params.DynamicFeeExtraDataSize:], nil + return extraData[params.DynamicFeeExtraDataSize:], true } diff --git a/predicate/predicate_bytes_test.go b/predicate/predicate_bytes_test.go index 9742ed2858..d712fd11d5 100644 --- a/predicate/predicate_bytes_test.go +++ b/predicate/predicate_bytes_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/ava-labs/avalanchego/utils" + "github.com/ava-labs/subnet-evm/params" "github.com/stretchr/testify/require" ) @@ -48,3 +49,18 @@ func TestUnpackInvalidPredicate(t *testing.T) { } } } + +func TestPredicateResultsBytes(t *testing.T) { + require := require.New(t) + dataTooShort := utils.RandomBytes(params.DynamicFeeExtraDataSize - 1) + _, ok := GetPredicateResultBytes(dataTooShort) + require.False(ok) + + preDUPgradeData := utils.RandomBytes(params.DynamicFeeExtraDataSize) + _, ok = GetPredicateResultBytes(preDUPgradeData) + require.False(ok) + postDUpgradeData := utils.RandomBytes(params.DynamicFeeExtraDataSize + 2) + resultBytes, ok := GetPredicateResultBytes(postDUpgradeData) + require.True(ok) + require.Equal(resultBytes, postDUpgradeData[params.DynamicFeeExtraDataSize:]) +} From eccd2eee940e263279df88eb0fc28180e986131b Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 26 Oct 2023 19:42:12 +0300 Subject: [PATCH 52/52] bump avalanchego to v1.10.14 (#971) * bump avalanchego to v1.10.14 * fix versions in readme * bump min golang version * bump anr --- .../workflows/auto-generated-code-checker.yml | 2 +- .github/workflows/bench.yml | 2 +- .github/workflows/ci.yml | 6 +-- .github/workflows/release.yml | 2 +- Dockerfile | 2 +- README.md | 5 ++- compatibility.json | 1 + go.mod | 14 ++++--- go.sum | 38 +++++++++++++------ plugin/evm/version.go | 2 +- scripts/build.sh | 2 +- scripts/versions.sh | 2 +- tests/utils/runner/network_manager.go | 2 +- 13 files changed, 50 insertions(+), 30 deletions(-) diff --git a/.github/workflows/auto-generated-code-checker.yml b/.github/workflows/auto-generated-code-checker.yml index f8d372d3f0..39bd2c8434 100644 --- a/.github/workflows/auto-generated-code-checker.yml +++ b/.github/workflows/auto-generated-code-checker.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: - go-version: '~1.20.8' + go-version: '~1.20.10' check-latest: true - shell: bash run: scripts/mock.gen.sh diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index c6a67b266f..12ce8eec47 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -11,7 +11,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: - go-version: '~1.20.8' + go-version: '~1.20.10' check-latest: true - run: go mod download shell: bash diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35f03e39d5..d389ac5b50 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: shell: bash - uses: actions/setup-go@v3 with: - go-version: '~1.20.8' + go-version: '~1.20.10' check-latest: true - name: golangci-lint uses: golangci/golangci-lint-action@v3 @@ -34,7 +34,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: - go-version: '~1.20.8' + go-version: '~1.20.10' check-latest: true - run: go mod download shell: bash @@ -66,7 +66,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: '~1.20.8' + go-version: '~1.20.10' check-latest: true - name: Use Node.js uses: actions/setup-node@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 402eef6bb5..febc34fa75 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: '~1.20.8' + go-version: '~1.20.10' check-latest: true - name: Set up arm64 cross compiler run: | diff --git a/Dockerfile b/Dockerfile index 13fdd428f2..11784758dd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ ARG AVALANCHE_VERSION # ============= Compilation Stage ================ -FROM golang:1.20.8-bullseye AS builder +FROM golang:1.20.10-bullseye AS builder WORKDIR /build diff --git a/README.md b/README.md index c3e8b641e3..86c966f8a4 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,8 @@ The Subnet EVM runs in a separate process from the main AvalancheGo process and [v0.5.4] AvalancheGo@v1.10.9-v1.10.12 (Protocol Version: 28) [v0.5.5] AvalancheGo@v1.10.9-v1.10.12 (Protocol Version: 28) [v0.5.6] AvalancheGo@v1.10.9-v1.10.12 (Protocol Version: 28) -[v0.5.7] AvalancheGo@v1.10.13-v1.10.13 (Protocol Version: 29) +[v0.5.7] AvalancheGo@v1.10.13-v1.10.14 (Protocol Version: 29) +[v0.5.8] AvalancheGo@v1.10.13-v1.10.14 (Protocol Version: 29) ``` ## API @@ -91,7 +92,7 @@ To support these changes, there have been a number of changes to the SubnetEVM b ### Clone Subnet-evm -First install Go 1.20.8 or later. Follow the instructions [here](https://go.dev/doc/install). You can verify by running `go version`. +First install Go 1.20.10 or later. Follow the instructions [here](https://go.dev/doc/install). You can verify by running `go version`. Set `$GOPATH` environment variable properly for Go to look for Go Workspaces. Please read [this](https://go.dev/doc/code) for details. You can verify by running `echo $GOPATH`. diff --git a/compatibility.json b/compatibility.json index f3df6679de..179e74ea02 100644 --- a/compatibility.json +++ b/compatibility.json @@ -1,5 +1,6 @@ { "rpcChainVMProtocolVersion": { + "v0.5.8": 29, "v0.5.7": 29, "v0.5.6": 28, "v0.5.5": 28, diff --git a/go.mod b/go.mod index 00a1d94e7e..393d837826 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.20 require ( github.com/VictoriaMetrics/fastcache v1.10.0 - github.com/ava-labs/avalanche-network-runner v1.7.2 - github.com/ava-labs/avalanchego v1.10.13 + github.com/ava-labs/avalanche-network-runner v1.7.3-0.20231026153158-2931f8a448d9 + github.com/ava-labs/avalanchego v1.10.14 github.com/cespare/cp v0.1.0 github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 github.com/davecgh/go-spew v1.1.1 @@ -55,7 +55,7 @@ require ( github.com/DataDog/zstd v1.5.2 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect - github.com/ava-labs/coreth v0.12.6-rc.2 // indirect + github.com/ava-labs/coreth v0.12.7-rc.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.7.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect @@ -94,6 +94,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/huin/goupnp v1.0.3 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/influxdata/influxdb-client-go/v2 v2.4.0 // indirect github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c // indirect github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // indirect @@ -120,11 +121,12 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect - github.com/rogpeppe/go-internal v1.9.0 // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/rs/cors v1.7.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/afero v1.8.2 // indirect + github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.3.0 // indirect github.com/supranational/blst v0.3.11 // indirect @@ -143,8 +145,8 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect - golang.org/x/mod v0.10.0 // indirect + golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect + golang.org/x/mod v0.11.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/term v0.13.0 // indirect gonum.org/v1/gonum v0.11.0 // indirect diff --git a/go.sum b/go.sum index f23ab9ef18..a92c797c98 100644 --- a/go.sum +++ b/go.sum @@ -38,7 +38,7 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= @@ -61,10 +61,12 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/ava-labs/avalanche-network-runner v1.7.2 h1:XFad/wZfYzDnqbLzPAPPRYU3a1Zc8QT8x5dtLTS3lUo= github.com/ava-labs/avalanche-network-runner v1.7.2/go.mod h1:naLveusSrP7YgTAqRykD1SyLOAUilCp9jGjk3MDxoPI= -github.com/ava-labs/avalanchego v1.10.13 h1:LJQoV20Rw1/D/anP7RTTTmzgvwzx3DKEfe1BZp3Szxg= -github.com/ava-labs/avalanchego v1.10.13/go.mod h1:tbPGmDognRVE/6xqqal/fszBZlr0jyll3KXXO/7cuqo= -github.com/ava-labs/coreth v0.12.6-rc.2 h1:5P9/i3h6g2uUYf6BVhG8YrV6iqhov6vRxph6cvuKpvU= -github.com/ava-labs/coreth v0.12.6-rc.2/go.mod h1:sNbwitXv4AhLvWpSqy6V8yzkhGFeWBQFD31/xiRDJ5M= +github.com/ava-labs/avalanche-network-runner v1.7.3-0.20231026153158-2931f8a448d9 h1:mTxfmBxmV8GSW0PUt0Qj/Obetzsw0gP6dtoNHaKW2+o= +github.com/ava-labs/avalanche-network-runner v1.7.3-0.20231026153158-2931f8a448d9/go.mod h1:M9FC+xU4hQU3Botux8V8j574YETeX6tMvxiOmqLfl5c= +github.com/ava-labs/avalanchego v1.10.14 h1:1jTMrikYD49Pb64ZLUi2z2BnNGLzIGip4fValq6/YfE= +github.com/ava-labs/avalanchego v1.10.14/go.mod h1:En/ti2xoxQqJuN6t9ne2ogckU9leuZzTjl5mbEsfjTc= +github.com/ava-labs/coreth v0.12.7-rc.1 h1:fvjow2Jqkq1RNtW4v2Kx0DdTVp+3+fCY421TxpDDRfM= +github.com/ava-labs/coreth v0.12.7-rc.1/go.mod h1:sNbwitXv4AhLvWpSqy6V8yzkhGFeWBQFD31/xiRDJ5M= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -131,6 +133,7 @@ github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoG github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811/go.mod h1:Nb5lgvnQ2+oGlE/EyZy4+2/CxRh9KfvCXnag1vtpxVM= github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= @@ -194,7 +197,7 @@ github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= @@ -220,6 +223,7 @@ github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxI github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -372,6 +376,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb-client-go/v2 v2.4.0 h1:HGBfZYStlx3Kqvsv1h2pJixbCl/jhnFtxpKFAv9Tu5k= github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c h1:qSHzRbhzK8RdXOsAdfDgO49TtqC1oZ+acxPrkfTxcCs= @@ -390,13 +396,16 @@ github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7Bd github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= @@ -427,6 +436,7 @@ github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4F github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= @@ -468,9 +478,11 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= @@ -537,8 +549,9 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -564,6 +577,8 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -578,6 +593,7 @@ github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -691,8 +707,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= -golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= +golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -719,8 +735,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/plugin/evm/version.go b/plugin/evm/version.go index d5489ea05e..0977bb0529 100644 --- a/plugin/evm/version.go +++ b/plugin/evm/version.go @@ -11,7 +11,7 @@ var ( // GitCommit is set by the build script GitCommit string // Version is the version of Subnet EVM - Version string = "v0.5.7" + Version string = "v0.5.8" ) func init() { diff --git a/scripts/build.sh b/scripts/build.sh index d18be270ee..c9b169bee9 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -4,7 +4,7 @@ set -o errexit set -o nounset set -o pipefail -go_version_minimum="1.20.8" +go_version_minimum="1.20.10" go_version() { go version | sed -nE -e 's/[^0-9.]+([0-9.]+).+/\1/p' diff --git a/scripts/versions.sh b/scripts/versions.sh index cec3bad0b8..244ad74069 100644 --- a/scripts/versions.sh +++ b/scripts/versions.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Don't export them as they're used in the context of other calls -AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.10.13'} +AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.10.14'} AVALANCHEGO_VERSION=${AVALANCHEGO_VERSION:-$AVALANCHE_VERSION} GINKGO_VERSION=${GINKGO_VERSION:-'v2.2.0'} diff --git a/tests/utils/runner/network_manager.go b/tests/utils/runner/network_manager.go index 81b7d9dcaf..f1fd5993a5 100644 --- a/tests/utils/runner/network_manager.go +++ b/tests/utils/runner/network_manager.go @@ -365,7 +365,7 @@ func RegisterFiveNodeSubnetRun() func() *Subnet { gomega.Expect(err).Should(gomega.BeNil()) }) - var _ = ginkgo.AfterSuite(func() { + _ = ginkgo.AfterSuite(func() { gomega.Expect(manager).ShouldNot(gomega.BeNil()) gomega.Expect(manager.TeardownNetwork()).Should(gomega.BeNil()) // TODO: bootstrap an additional node to ensure that we can bootstrap the test data correctly