-
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
3 changed files
with
270 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
// (c) Cartesi and individual authors (see AUTHORS) | ||
// SPDX-License-Identifier: Apache-2.0 (see LICENSE) | ||
|
||
package main | ||
|
||
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( | ||
"cartesi machine 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 "", ReadHashError{Err: err} | ||
} else if len(hash) != common.HashLength { | ||
return "", MalformedHashError{ReadBytes: 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 "", err | ||
} | ||
cartesiApplication, err := contracts.NewCartesiDAppCaller( | ||
common.HexToAddress(applicationAddress), | ||
client, | ||
) | ||
if err != nil { | ||
return "", err | ||
} | ||
hash, err := cartesiApplication.GetTemplateHash(&bind.CallOpts{Context: ctx}) | ||
if err != nil { | ||
return "", err | ||
} | ||
return common.Bytes2Hex(hash[:]), nil | ||
} | ||
|
||
// ------------------------------------------------------------------------------------------------ | ||
// Custom errors | ||
// ------------------------------------------------------------------------------------------------ | ||
|
||
type ReadHashError struct { | ||
Err error | ||
} | ||
|
||
func (e ReadHashError) Error() string { | ||
return "failed to read hash file: " + e.Err.Error() | ||
} | ||
|
||
type MalformedHashError struct { | ||
ReadBytes int | ||
} | ||
|
||
func (e MalformedHashError) Error() string { | ||
return fmt.Sprintf( | ||
"malformed hash: expected %v bytes, read %v", | ||
common.HashLength, | ||
e.ReadBytes, | ||
) | ||
} |
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,156 @@ | ||
// (c) Cartesi and individual authors (see AUTHORS) | ||
// SPDX-License-Identifier: Apache-2.0 (see LICENSE) | ||
|
||
package main | ||
|
||
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" | ||
) | ||
|
||
type ValidateMachineHashSuite struct { | ||
suite.Suite | ||
} | ||
|
||
func TestValidateMachineHash(t *testing.T) { | ||
suite.Run(t, new(ValidateMachineHashSuite)) | ||
} | ||
|
||
func (s *ValidateMachineHashSuite) TestItFailsWhenSnapshotHasNoHash() { | ||
machineDir, err := os.MkdirTemp("", "") | ||
if err != nil { | ||
s.FailNow(err.Error()) | ||
} | ||
|
||
err = validateMachineHash(context.Background(), machineDir, "", "") | ||
s.NotNil(err) | ||
s.IsType(ReadHashError{}, err) | ||
|
||
os.RemoveAll(machineDir) | ||
} | ||
|
||
func (s *ValidateMachineHashSuite) TestItFailsWhenHashHasWrongSize() { | ||
machineDir, err := mockMachineDir("deadbeef") | ||
if err != nil { | ||
s.FailNow(err.Error()) | ||
} | ||
|
||
err = validateMachineHash(context.Background(), machineDir, "", "") | ||
s.NotNil(err) | ||
s.IsType(MalformedHashError{}, err) | ||
|
||
os.RemoveAll(machineDir) | ||
} | ||
|
||
func (s *ValidateMachineHashSuite) TestItFailsWhenContextIsCanceled() { | ||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) | ||
defer cancel() | ||
machineDir, err := createMachineSnapshot() | ||
if err != nil { | ||
s.FailNow(err.Error()) | ||
} | ||
devnet, err := startDevnet() | ||
if err != nil { | ||
s.FailNow(err.Error()) | ||
} | ||
|
||
err = validateMachineHash( | ||
ctx, | ||
machineDir, | ||
addresses.GetTestBook().CartesiDApp.String(), | ||
"http://0.0.0.0:"+deps.DefaultDevnetPort, | ||
) | ||
s.NotNil(err) | ||
s.ErrorIs(err, context.DeadlineExceeded) | ||
|
||
os.RemoveAll(machineDir) | ||
err = deps.Terminate(context.Background(), devnet) | ||
if err != nil { | ||
s.FailNow(err.Error()) | ||
} | ||
} | ||
|
||
func (s *ValidateMachineHashSuite) TestItSucceedsWhenHashesAreEqual() { | ||
ctx := context.Background() | ||
machineDir, err := createMachineSnapshot() | ||
if err != nil { | ||
s.FailNow(err.Error()) | ||
} | ||
devnet, err := startDevnet() | ||
if err != nil { | ||
s.FailNow(err.Error()) | ||
} | ||
|
||
if err := validateMachineHash( | ||
ctx, | ||
machineDir, | ||
addresses.GetTestBook().CartesiDApp.String(), | ||
"http://0.0.0.0:"+deps.DefaultDevnetPort, | ||
); err != nil { | ||
s.FailNow(err.Error()) | ||
} | ||
|
||
os.RemoveAll(machineDir) | ||
err = deps.Terminate(ctx, devnet) | ||
if 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 return | ||
// 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