Skip to content

Commit

Permalink
Add warp contract implementation (#718)
Browse files Browse the repository at this point in the history
* Add warp contract implementation

* Cleanup predicate test

* Fix new function signature

* Replace invalid fuzz test with unit test

* Add chain config to enable warp API for warp e2e test

* remove unused var

* Add experimental warning and move warp precompile to x/ package

* fix warning label

* Fix warning

* vm test nits

* Improve sendWarpMessenger sol comment

* more vm warp test nits

* Move warp params into params package

* More vm warp test nits

* Address more PR comments

* Remove triggerTx2

* Add check for expected topics from sendWarpMessage log

* Fix config test

* Fix incorrect replace

* remove unnecessary echo

* Address comments

* Address comments

* Address PR comments

* Improve comments

* Convert [32]byte type to common.Hash

* Add base cost for getVerifiedWarpMessage

* fix require equal type check

* Fix updated awm message format

* Update warp message format

* Move IWarpMessenger.sol to interfaces/

* Add newline to warp genesis

* uncomment assertion

* Fix broken links in README
  • Loading branch information
aaronbuchwald authored Jul 31, 2023
1 parent 402510e commit 5dbfa82
Show file tree
Hide file tree
Showing 27 changed files with 3,660 additions and 46 deletions.
42 changes: 42 additions & 0 deletions contracts/contracts/ExampleWarp.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;

import "./interfaces/IWarpMessenger.sol";

contract ExampleWarp {
address constant WARP_ADDRESS = 0x0200000000000000000000000000000000000005;
WarpMessenger warp = WarpMessenger(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);
}


// validateWarpMessage retrieves the warp message attached to the transaction and verifies all of its attributes.
function validateWarpMessage(
bytes32 originChainID,
address originSenderAddress,
bytes32 destinationChainID,
address destinationAddress,
bytes calldata payload
) external view {
(WarpMessage memory message, bool exists) = warp.getVerifiedWarpMessage();
require(exists);
require(message.originChainID == originChainID);
require(message.originSenderAddress == originSenderAddress);
require(message.destinationChainID == destinationChainID);
require(message.destinationAddress == destinationAddress);
require(keccak256(message.payload) == keccak256(payload));
}

// validateGetBlockchainID checks that the blockchainID returned by warp matches the argument
function validateGetBlockchainID(bytes32 blockchainID) external view {
require(blockchainID == warp.getBlockchainID());
}
}
56 changes: 56 additions & 0 deletions contracts/contracts/interfaces/IWarpMessenger.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// (c) 2022-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

struct WarpMessage {
bytes32 originChainID;
address originSenderAddress;
bytes32 destinationChainID;
address destinationAddress;
bytes payload;
}

interface WarpMessenger {
event SendWarpMessage(
bytes32 indexed destinationChainID,
address indexed destinationAddress,
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(
bytes32 destinationChainID,
address destinationAddress,
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.
// Returns false if no such predicate exists.
function getVerifiedWarpMessage()
external view
returns (WarpMessage calldata message, bool exists);

// Note: getVerifiedWarpMessage takes no arguments because it returns a single verified
// message that is encoded in the predicate (inside the tx access list) of the transaction.
// The alternative design to this is to verify messages during the EVM's execution in which
// case there would be no predicate and the block would encode the hits/misses that occur
// throughout its execution.
// This would result in the following alternative function signature:
// function verifyMessage(bytes calldata signedWarpMsg) external returns (WarpMessage calldata message);

// 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);
}
27 changes: 14 additions & 13 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,19 +159,20 @@ func NewWithSnapshot(root common.Hash, db Database, snap snapshot.Snapshot) (*St
return nil, err
}
sdb := &StateDB{
db: db,
trie: tr,
originalRoot: root,
stateObjects: make(map[common.Address]*stateObject),
stateObjectsPending: make(map[common.Address]struct{}),
stateObjectsDirty: make(map[common.Address]struct{}),
stateObjectsDestruct: make(map[common.Address]struct{}),
logs: make(map[common.Hash][]*types.Log),
preimages: make(map[common.Hash][]byte),
journal: newJournal(),
accessList: newAccessList(),
transientStorage: newTransientStorage(),
hasher: crypto.NewKeccakState(),
db: db,
trie: tr,
originalRoot: root,
stateObjects: make(map[common.Address]*stateObject),
stateObjectsPending: make(map[common.Address]struct{}),
stateObjectsDirty: make(map[common.Address]struct{}),
stateObjectsDestruct: make(map[common.Address]struct{}),
logs: make(map[common.Hash][]*types.Log),
preimages: make(map[common.Hash][]byte),
journal: newJournal(),
predicateStorageSlots: make(map[common.Address][]byte),
accessList: newAccessList(),
transientStorage: newTransientStorage(),
hasher: crypto.NewKeccakState(),
}
if snap != nil {
if snap.Root() != root {
Expand Down
4 changes: 3 additions & 1 deletion params/avalanche_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
package params

const (
WarpQuorumDenominator uint64 = 100
WarpDefaultQuorumNumerator uint64 = 67
WarpQuorumNumeratorMinimum uint64 = 33
WarpQuorumDenominator uint64 = 100
)
8 changes: 4 additions & 4 deletions plugin/evm/network_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import (
syncHandlers "github.com/ava-labs/subnet-evm/sync/handlers"
syncStats "github.com/ava-labs/subnet-evm/sync/handlers/stats"
"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{}
Expand All @@ -31,17 +33,15 @@ func newNetworkHandler(
provider syncHandlers.SyncDataProvider,
diskDB ethdb.KeyValueReader,
evmTrieDB *trie.Database,
warpBackend warp.WarpBackend,
networkCodec codec.Manager,
) message.RequestHandler {
syncStats := syncStats.NewHandlerStats(metrics.Enabled)
return &networkHandler{
// State sync handlers
stateTrieLeafsRequestHandler: syncHandlers.NewLeafsRequestHandler(evmTrieDB, provider, networkCodec, syncStats),
blockRequestHandler: syncHandlers.NewBlockRequestHandler(provider, networkCodec, syncStats),
codeRequestHandler: syncHandlers.NewCodeRequestHandler(diskDB, networkCodec, syncStats),

// TODO: initialize actual signature request handler when warp is ready
signatureRequestHandler: &warpHandlers.NoopSignatureRequestHandler{},
signatureRequestHandler: warpHandlers.NewSignatureRequestHandler(warpBackend, networkCodec, warpStats.NewStats()),
}
}

Expand Down
2 changes: 1 addition & 1 deletion plugin/evm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@ func (vm *VM) setAppRequestHandlers() {
},
)

networkHandler := newNetworkHandler(vm.blockChain, vm.chaindb, evmTrieDB, vm.networkCodec)
networkHandler := newNetworkHandler(vm.blockChain, vm.chaindb, evmTrieDB, vm.warpBackend, vm.networkCodec)
vm.Network.SetRequestHandler(networkHandler)
}

Expand Down
59 changes: 48 additions & 11 deletions plugin/evm/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3156,24 +3156,61 @@ func TestCrossChainMessagestoVM(t *testing.T) {
}

func TestSignatureRequestsToVM(t *testing.T) {
_, vm, _, _ := GenesisVM(t, true, genesisJSONSubnetEVM, "", "")
_, vm, _, appSender := GenesisVM(t, true, genesisJSONSubnetEVM, "", "")

defer func() {
err := vm.Shutdown(context.Background())
require.NoError(t, err)
}()

// Generate a SignatureRequest for an unknown message
var signatureRequest message.Request = message.SignatureRequest{
MessageID: ids.GenerateTestID(),
}

requestBytes, err := message.Codec.Marshal(message.Version, &signatureRequest)
// Generate a new warp unsigned message and add to warp backend
warpMessage, err := avalancheWarp.NewUnsignedMessage(vm.ctx.NetworkID, vm.ctx.ChainID, []byte{1, 2, 3})
require.NoError(t, err)

// Currently with warp not being initialized we just need to make sure the NoopSignatureRequestHandler does not
// panic/crash when sent a SignatureRequest.
// TODO: We will need to update the test when warp is initialized to check for expected response.
err = vm.Network.AppRequest(context.Background(), ids.GenerateTestNodeID(), 1, time.Now().Add(60*time.Second), requestBytes)
// 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())
require.NoError(t, err)

tests := map[string]struct {
messageID ids.ID
expectedResponse [bls.SignatureLen]byte
}{
"known": {
messageID: warpMessage.ID(),
expectedResponse: signature,
},
"unknown": {
messageID: 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.SignatureRequest{
MessageID: test.messageID,
}

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)
})
}
}
Loading

0 comments on commit 5dbfa82

Please sign in to comment.