Deterministic hashes for ETH lockups

We're missing a nonce for truly unique hashes - for now, two
identical transfers will only be executed once.
This commit is contained in:
Leo 2020-08-17 19:29:25 +02:00
parent bc3714fc73
commit 7903402fa6
6 changed files with 65 additions and 36 deletions

View File

@ -141,6 +141,7 @@ func main() {
zap.String("source", hex.EncodeToString(k.SourceAddress[:])), zap.String("source", hex.EncodeToString(k.SourceAddress[:])),
zap.String("target", hex.EncodeToString(k.TargetAddress[:])), zap.String("target", hex.EncodeToString(k.TargetAddress[:])),
zap.String("amount", k.Amount.String()), zap.String("amount", k.Amount.String()),
zap.String("hash", hex.EncodeToString(k.Hash())),
) )
} }
} }

View File

@ -0,0 +1,5 @@
package common
type BridgeWatcher interface {
WatchLockups(events chan *ChainLock) error
}

View File

@ -0,0 +1,46 @@
package common
import (
"crypto/sha256"
"encoding/json"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/certusone/wormhole/bridge/pkg/vaa"
)
type ChainLock struct {
TxHash common.Hash
SourceAddress vaa.Address
TargetAddress vaa.Address
SourceChain vaa.ChainID
TargetChain vaa.ChainID
TokenChain vaa.ChainID
TokenAddress vaa.Address
Amount *big.Int
}
// Hash returns a deterministic hash of the given ChainLock, meant to be used
// as primary key for the distributed round of signing.
//
// TODO: the ETH contract is missing a nonce, so it's not yet deterministic
func (l *ChainLock) Hash() []byte {
// TODO: json.Marshal being deterministic is an implementation detail - what guarantees do we need?
// We do not necessarily need stable serialization across releases, but they do need to be unique.
b, err := json.Marshal(l)
if err != nil {
panic(err)
}
h := sha256.New()
h.Write(b)
return h.Sum(nil)
}

View File

@ -1,25 +0,0 @@
package common
import (
"github.com/certusone/wormhole/bridge/pkg/vaa"
"math/big"
)
type (
BridgeWatcher interface {
WatchLockups(events chan *ChainLock) error
}
ChainLock struct {
SourceAddress vaa.Address
TargetAddress vaa.Address
SourceChain vaa.ChainID
TargetChain vaa.ChainID
TokenChain vaa.ChainID
TokenAddress vaa.Address
Amount *big.Int
}
)

View File

@ -3,17 +3,19 @@ package ethereum
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/certusone/wormhole/bridge/pkg/common" "sync"
"github.com/certusone/wormhole/bridge/pkg/ethereum/abi" "time"
"github.com/certusone/wormhole/bridge/pkg/supervisor"
"github.com/certusone/wormhole/bridge/pkg/vaa"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
eth_common "github.com/ethereum/go-ethereum/common" eth_common "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"go.uber.org/zap" "go.uber.org/zap"
"sync"
"time" "github.com/certusone/wormhole/bridge/pkg/common"
"github.com/certusone/wormhole/bridge/pkg/ethereum/abi"
"github.com/certusone/wormhole/bridge/pkg/supervisor"
"github.com/certusone/wormhole/bridge/pkg/vaa"
) )
type ( type (
@ -30,8 +32,6 @@ type (
pendingLock struct { pendingLock struct {
lock *common.ChainLock lock *common.ChainLock
txHash eth_common.Hash
height uint64 height uint64
} }
) )
@ -73,6 +73,7 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error {
return return
case ev := <-sink: case ev := <-sink:
lock := &common.ChainLock{ lock := &common.ChainLock{
TxHash: ev.Raw.TxHash,
SourceAddress: ev.Sender, SourceAddress: ev.Sender,
TargetAddress: ev.Recipient, TargetAddress: ev.Recipient,
SourceChain: vaa.ChainIDEthereum, SourceChain: vaa.ChainIDEthereum,
@ -87,7 +88,6 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error {
e.pendingLocksGuard.Lock() e.pendingLocksGuard.Lock()
e.pendingLocks[ev.Raw.TxHash] = &pendingLock{ e.pendingLocks[ev.Raw.TxHash] = &pendingLock{
lock: lock, lock: lock,
txHash: ev.Raw.TxHash,
height: ev.Raw.BlockNumber, height: ev.Raw.BlockNumber,
} }
e.pendingLocksGuard.Unlock() e.pendingLocksGuard.Unlock()
@ -121,7 +121,7 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error {
// Transaction was dropped and never picked up again // Transaction was dropped and never picked up again
if pLock.height+4*e.minConfirmations <= blockNumberU { if pLock.height+4*e.minConfirmations <= blockNumberU {
logger.Debug("lockup timed out", zap.Stringer("tx", pLock.txHash), logger.Debug("lockup timed out", zap.Stringer("tx", pLock.lock.TxHash),
zap.Stringer("number", ev.Number)) zap.Stringer("number", ev.Number))
delete(e.pendingLocks, hash) delete(e.pendingLocks, hash)
continue continue
@ -129,7 +129,7 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error {
// Transaction is now ready // Transaction is now ready
if pLock.height+e.minConfirmations <= ev.Number.Uint64() { if pLock.height+e.minConfirmations <= ev.Number.Uint64() {
logger.Debug("lockup confirmed", zap.Stringer("tx", pLock.txHash), logger.Debug("lockup confirmed", zap.Stringer("tx", pLock.lock.TxHash),
zap.Stringer("number", ev.Number)) zap.Stringer("number", ev.Number))
delete(e.pendingLocks, hash) delete(e.pendingLocks, hash)
e.evChan <- pLock.lock e.evChan <- pLock.lock

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache 2 // SPDX-License-Identifier: Apache 2
// TODO(hendrik): reentrancy protection for all methods // TODO(hendrik): reentrancy protection for all methods
// TODO(hendrik): switch-over feature
pragma solidity ^0.6.0; pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
@ -197,6 +198,7 @@ contract Wormhole {
isWrappedAsset[asset] = true; isWrappedAsset[asset] = true;
} }
// TODO(hendrik): nonce
function lockAssets( function lockAssets(
address asset, address asset,
uint256 amount, uint256 amount,