-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
244 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
// (c) Cartesi and individual authors (see AUTHORS) | ||
// SPDX-License-Identifier: Apache-2.0 (see LICENSE) | ||
|
||
package node | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
"path" | ||
|
||
"github.com/cartesi/rollups-node/pkg/contracts" | ||
"github.com/ethereum/go-ethereum/accounts/abi/bind" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/ethclient" | ||
) | ||
|
||
// Validates if the hash from the Cartesi Machine at machineDir matches the template hash onchain. | ||
// It returns an error if it doesn't. | ||
func validateMachineHash( | ||
ctx context.Context, | ||
machineDir string, | ||
applicationAddress string, | ||
ethereumNodeAddr string, | ||
) error { | ||
offchainHash, err := readHash(machineDir) | ||
if err != nil { | ||
return err | ||
} | ||
onchainHash, err := getTemplateHash(ctx, applicationAddress, ethereumNodeAddr) | ||
if err != nil { | ||
return err | ||
} | ||
if offchainHash != onchainHash { | ||
return fmt.Errorf( | ||
"validate machine hash: hash mismatch; expected %v but got %v", | ||
onchainHash, | ||
offchainHash, | ||
) | ||
} | ||
return nil | ||
} | ||
|
||
// Reads the Cartesi Machine hash from machineDir. Returns it as a hex string or | ||
// an error | ||
func readHash(machineDir string) (string, error) { | ||
path := path.Join(machineDir, "hash") | ||
hash, err := os.ReadFile(path) | ||
if err != nil { | ||
return "", fmt.Errorf("read hash: %w", err) | ||
} else if len(hash) != common.HashLength { | ||
return "", fmt.Errorf( | ||
"read hash: wrong size; expected %v bytes but read %v", | ||
common.HashLength, | ||
len(hash), | ||
) | ||
} | ||
return common.Bytes2Hex(hash), nil | ||
} | ||
|
||
// Retrieves the template hash from the application contract. Returns it as a | ||
// hex string or an error | ||
func getTemplateHash( | ||
ctx context.Context, | ||
applicationAddress string, | ||
ethereumNodeAddr string, | ||
) (string, error) { | ||
client, err := ethclient.DialContext(ctx, ethereumNodeAddr) | ||
if err != nil { | ||
return "", fmt.Errorf("get template hash: %w", err) | ||
} | ||
cartesiApplication, err := contracts.NewCartesiDAppCaller( | ||
common.HexToAddress(applicationAddress), | ||
client, | ||
) | ||
if err != nil { | ||
return "", fmt.Errorf("get template hash: %w", err) | ||
} | ||
hash, err := cartesiApplication.GetTemplateHash(&bind.CallOpts{Context: ctx}) | ||
if err != nil { | ||
return "", fmt.Errorf("get template hash: %w", err) | ||
} | ||
return common.Bytes2Hex(hash[:]), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
// (c) Cartesi and individual authors (see AUTHORS) | ||
// SPDX-License-Identifier: Apache-2.0 (see LICENSE) | ||
|
||
package node | ||
|
||
import ( | ||
"context" | ||
"os" | ||
"testing" | ||
"time" | ||
|
||
"github.com/cartesi/rollups-node/internal/deps" | ||
"github.com/cartesi/rollups-node/internal/machine" | ||
"github.com/cartesi/rollups-node/pkg/addresses" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/stretchr/testify/suite" | ||
) | ||
|
||
const BlockchainHttpEndpoint = "http://0.0.0.0:" + deps.DefaultDevnetPort | ||
|
||
type ValidateMachineHashSuite struct { | ||
suite.Suite | ||
} | ||
|
||
func TestValidateMachineHash(t *testing.T) { | ||
suite.Run(t, new(ValidateMachineHashSuite)) | ||
} | ||
|
||
func (s *ValidateMachineHashSuite) TestItFailsWhenSnapshotHasNoHash() { | ||
machineDir, err := os.MkdirTemp("", "") | ||
defer os.RemoveAll(machineDir) | ||
if err != nil { | ||
s.FailNow(err.Error()) | ||
} | ||
|
||
err = validateMachineHash(context.Background(), machineDir, "", "") | ||
s.ErrorContains(err, "no such file or directory") | ||
} | ||
|
||
func (s *ValidateMachineHashSuite) TestItFailsWhenHashHasWrongSize() { | ||
machineDir, err := mockMachineDir("deadbeef") | ||
defer os.RemoveAll(machineDir) | ||
if err != nil { | ||
s.FailNow(err.Error()) | ||
} | ||
|
||
err = validateMachineHash(context.Background(), machineDir, "", "") | ||
s.ErrorContains(err, "wrong size") | ||
} | ||
|
||
func (s *ValidateMachineHashSuite) TestItFailsWhenContextIsCanceled() { | ||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) | ||
defer cancel() | ||
machineDir, err := createMachineSnapshot() | ||
defer os.RemoveAll(machineDir) | ||
if err != nil { | ||
s.FailNow(err.Error()) | ||
} | ||
devnet, err := startDevnet() | ||
defer deps.Terminate(context.Background(), devnet) | ||
if err != nil { | ||
s.FailNow(err.Error()) | ||
} | ||
|
||
err = validateMachineHash( | ||
ctx, | ||
machineDir, | ||
addresses.GetTestBook().CartesiDApp.String(), | ||
BlockchainHttpEndpoint, | ||
) | ||
s.NotNil(err) | ||
s.ErrorIs(err, context.DeadlineExceeded) | ||
} | ||
|
||
func (s *ValidateMachineHashSuite) TestItSucceedsWhenHashesAreEqual() { | ||
ctx := context.Background() | ||
machineDir, err := createMachineSnapshot() | ||
defer os.RemoveAll(machineDir) | ||
if err != nil { | ||
s.FailNow(err.Error()) | ||
} | ||
devnet, err := startDevnet() | ||
defer deps.Terminate(context.Background(), devnet) | ||
if err != nil { | ||
s.FailNow(err.Error()) | ||
} | ||
|
||
if err := validateMachineHash( | ||
ctx, | ||
machineDir, | ||
addresses.GetTestBook().CartesiDApp.String(), | ||
BlockchainHttpEndpoint, | ||
); err != nil { | ||
s.FailNow(err.Error()) | ||
} | ||
} | ||
|
||
// ------------------------------------------------------------------------------------------------ | ||
// Auxiliary functions | ||
// ------------------------------------------------------------------------------------------------ | ||
|
||
// Mocks the Cartesi Machine directory by creating a temporary directory with | ||
// a single file named "hash" with the contents of `hash`, a hexadecimal string | ||
func mockMachineDir(hash string) (string, error) { | ||
temp, err := os.MkdirTemp("", "") | ||
if err != nil { | ||
return "", err | ||
} | ||
hashFile := temp + "/hash" | ||
err = os.WriteFile(hashFile, common.FromHex(hash), os.ModePerm) | ||
if err != nil { | ||
return "", err | ||
} | ||
return temp, nil | ||
} | ||
|
||
// Generates a new Cartesi Machine snapshot in a temporary directory and returns | ||
// its path | ||
func createMachineSnapshot() (string, error) { | ||
tmpDir, err := os.MkdirTemp("", "") | ||
if err != nil { | ||
return "", err | ||
} | ||
if err = machine.Save( | ||
"cartesi/rollups-node-snapshot:devel", | ||
tmpDir, | ||
"snapshotTemp", | ||
); err != nil { | ||
return "", err | ||
} | ||
return tmpDir, nil | ||
} | ||
|
||
// Starts a devnet in a Docker container with the default parameters | ||
func startDevnet() (*deps.DepsContainers, error) { | ||
container, err := deps.Run(context.Background(), deps.DepsConfig{ | ||
Devnet: &deps.DevnetConfig{ | ||
DockerImage: deps.DefaultDevnetDockerImage, | ||
Port: deps.DefaultDevnetPort, | ||
}, | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return container, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters