Skip to content

Commit

Permalink
Merge pull request #339 from onflow/handle-direct-call-hash-calc-change
Browse files Browse the repository at this point in the history
Handle the hash calculation `DirectCall` change to maintain backwards compatibility
  • Loading branch information
m-Peter authored Jul 9, 2024
2 parents 50a7728 + 0b75260 commit 11f6124
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 23 deletions.
3 changes: 3 additions & 0 deletions bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ func Start(ctx context.Context, cfg *config.Config) error {
logger.Info().Msg("database initialized with 0 evm and cadence heights")
}

// TEMP: Remove `DirectCallHashCalculationBlockHeightChange` after PreviewNet is reset
models.DirectCallHashCalculationBlockHeightChange = cfg.HashCalculationHeightChange

go func() {
err := startServer(
ctx,
Expand Down
5 changes: 5 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ type Config struct {
WalletEnabled bool
// WalletKey used for signing transactions
WalletKey *ecdsa.PrivateKey
// TEMP: Remove `HashCalculationHeightChange` after PreviewNet is reset
HashCalculationHeightChange uint64
}

func FromFlags() (*Config, error) {
Expand Down Expand Up @@ -150,6 +152,9 @@ func FromFlags() (*Config, error) {
flag.StringVar(&cloudKMSKeyRingID, "coa-cloud-kms-key-ring-id", "", "The key ring ID where the KMS keys exist, e.g. 'tx-signing'")
flag.StringVar(&cloudKMSKeys, "coa-cloud-kms-keys", "", `Names of the KMS keys and their versions as a comma separated list, e.g. "gw-key-6@1,gw-key-7@1,gw-key-8@1"`)
flag.StringVar(&walletKey, "wallet-api-key", "", "ECDSA private key used for wallet APIs. WARNING: This should only be used locally or for testing, never in production.")
// TEMP: Only set this after the HCU containing the direct call
// hash calculation change has been successfully deployed.
flag.Uint64Var(&cfg.HashCalculationHeightChange, "hash-calc-height-change", 0, "Cadence height at which the direct call hash calculation changed")
flag.Parse()

if coinbase == "" {
Expand Down
26 changes: 23 additions & 3 deletions models/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,29 @@ var _ Transaction = &DirectCall{}

type DirectCall struct {
*types.DirectCall
// TEMP: Remove `blockHeight` after PreviewNet is reset
blockHeight uint64
}

// TEMP: Remove `DirectCallHashCalculationBlockHeightChange` after PreviewNet is reset
var DirectCallHashCalculationBlockHeightChange uint64 = 0

func (dc DirectCall) Hash() common.Hash {
return dc.DirectCall.Hash()
// Use the NEW hash calculation
if dc.blockHeight >= DirectCallHashCalculationBlockHeightChange {
return dc.DirectCall.Hash()
}

// Use the OLD hash calculation
tx := gethTypes.NewTx(&gethTypes.LegacyTx{
GasPrice: big.NewInt(0),
Gas: dc.GasLimit,
To: dc.To(),
Value: dc.Value(),
Data: dc.Data(),
Nonce: dc.Nonce(),
})
return tx.Hash()
}

func (dc DirectCall) RawSignatureValues() (
Expand Down Expand Up @@ -195,14 +214,15 @@ func decodeTransaction(event cadence.Event) (Transaction, error) {
return TransactionCall{Transaction: gethTx}, nil
}

func UnmarshalTransaction(value []byte) (Transaction, error) {
func UnmarshalTransaction(value []byte, blockHeight uint64) (Transaction, error) {
if value[0] == types.DirectCallTxType {
directCall, err := types.DirectCallFromEncoded(value)
if err != nil {
return nil, fmt.Errorf("failed to rlp decode direct call: %w", err)
}

return DirectCall{DirectCall: directCall}, nil
// TEMP: Remove `blockHeight` after PreviewNet is reset
return DirectCall{DirectCall: directCall, blockHeight: blockHeight}, nil
}

tx := &gethTypes.Transaction{}
Expand Down
64 changes: 62 additions & 2 deletions models/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ func Test_UnmarshalTransaction(t *testing.T) {
encodedTx, err := tx.MarshalBinary()
require.NoError(t, err)

decTx, err := UnmarshalTransaction(encodedTx)
decTx, err := UnmarshalTransaction(encodedTx, DirectCallHashCalculationBlockHeightChange)
require.NoError(t, err)
require.IsType(t, TransactionCall{}, decTx)

Expand Down Expand Up @@ -241,7 +241,7 @@ func Test_UnmarshalTransaction(t *testing.T) {
encodedTx, err := tx.MarshalBinary()
require.NoError(t, err)

decTx, err := UnmarshalTransaction(encodedTx)
decTx, err := UnmarshalTransaction(encodedTx, DirectCallHashCalculationBlockHeightChange)
require.NoError(t, err)
require.IsType(t, DirectCall{}, decTx)

Expand Down Expand Up @@ -279,6 +279,66 @@ func Test_UnmarshalTransaction(t *testing.T) {
assert.Equal(t, uint64(0), decTx.BlobGas())
assert.Equal(t, uint64(59), decTx.Size())
})

t.Run("with DirectCall hash calculation change", func(t *testing.T) {
t.Parallel()

DirectCallHashCalculationBlockHeightChange = 10

cdcEv, _ := createTestEvent(t, directCallBinary)

tx, err := decodeTransaction(cdcEv)
require.NoError(t, err)

encodedTx, err := tx.MarshalBinary()
require.NoError(t, err)

// blockHeight is greater than DirectCallHashCalculationBlockHeightChange
// which means we use the new hash calculation
decTx, err := UnmarshalTransaction(encodedTx, DirectCallHashCalculationBlockHeightChange+2)
require.NoError(t, err)
require.IsType(t, DirectCall{}, decTx)

v, r, s := decTx.RawSignatureValues()

from, err := decTx.From()
require.NoError(t, err)

newHash := decTx.Hash()

assert.Equal(
t,
gethCommon.HexToHash("0xb055748f36d6bbe99a7ab5e45202b5c095ceda985dec0cc2a8747fd88c80c8c9"),
newHash,
)
assert.Equal(t, big.NewInt(255), v)
assert.Equal(t, new(big.Int).SetBytes(from.Bytes()), r)
assert.Equal(t, big.NewInt(1), s)

// blockHeight is less than DirectCallHashCalculationBlockHeightChange
// which means we use the old hash calculation
decTx, err = UnmarshalTransaction(encodedTx, DirectCallHashCalculationBlockHeightChange-2)
require.NoError(t, err)
require.IsType(t, DirectCall{}, decTx)

v, r, s = decTx.RawSignatureValues()

from, err = decTx.From()
require.NoError(t, err)

oldHash := decTx.Hash()

assert.Equal(
t,
gethCommon.HexToHash("0xe090f3a66f269d436e4185551d790d923f53a2caabf475c18d60bf1f091813d9"),
oldHash,
)
assert.Equal(t, big.NewInt(255), v)
assert.Equal(t, new(big.Int).SetBytes(from.Bytes()), r)
assert.Equal(t, big.NewInt(1), s)

assert.NotEqual(t, newHash, oldHash)
})
}

func TestValidateTransaction(t *testing.T) {
Expand Down
13 changes: 12 additions & 1 deletion storage/pebble/transactions.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package pebble

import (
"encoding/binary"
"sync"

"github.com/onflow/go-ethereum/common"
Expand Down Expand Up @@ -46,5 +47,15 @@ func (t *Transactions) Get(ID common.Hash) (models.Transaction, error) {
return nil, err
}

return models.UnmarshalTransaction(val)
// TEMP: Remove this after PreviewNet is reset.
// Needed only for backwards compatibility with the
// direct call hash calculation breaking change.
heightVal, err := t.store.get(latestCadenceHeightKey)
if err != nil {
heightVal = []byte{0, 0, 0, 0, 0, 0, 0, 0}
}

cadenceHeight := binary.BigEndian.Uint64(heightVal)

return models.UnmarshalTransaction(val, cadenceHeight)
}
35 changes: 18 additions & 17 deletions tests/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,23 +137,24 @@ func servicesSetup(t *testing.T) (emulator.Emulator, func()) {

// default config
cfg := &config.Config{
DatabaseDir: t.TempDir(),
AccessNodeHost: "localhost:3569", // emulator
RPCPort: 8545,
RPCHost: "127.0.0.1",
FlowNetworkID: "flow-emulator",
EVMNetworkID: evmTypes.FlowEVMPreviewNetChainID,
Coinbase: common.HexToAddress(eoaTestAddress),
COAAddress: service.Address,
COAKey: service.PrivateKey,
CreateCOAResource: false,
GasPrice: new(big.Int).SetUint64(0),
LogLevel: zerolog.DebugLevel,
LogWriter: testLogWriter(),
StreamTimeout: time.Second * 30,
StreamLimit: 10,
RateLimit: 50,
WSEnabled: true,
DatabaseDir: t.TempDir(),
AccessNodeHost: "localhost:3569", // emulator
RPCPort: 8545,
RPCHost: "127.0.0.1",
FlowNetworkID: "flow-emulator",
EVMNetworkID: evmTypes.FlowEVMPreviewNetChainID,
Coinbase: common.HexToAddress(eoaTestAddress),
COAAddress: service.Address,
COAKey: service.PrivateKey,
CreateCOAResource: false,
GasPrice: new(big.Int).SetUint64(0),
LogLevel: zerolog.DebugLevel,
LogWriter: testLogWriter(),
StreamTimeout: time.Second * 30,
StreamLimit: 10,
RateLimit: 50,
WSEnabled: true,
HashCalculationHeightChange: 0,
}

go func() {
Expand Down

0 comments on commit 11f6124

Please sign in to comment.