Clean up docs and packages for v2

Change-Id: I1020e648f4a8bd51412cf06196e78665308efdac
This commit is contained in:
Leo 2021-07-21 19:46:10 +02:00
parent 5ad3814575
commit 052d922036
18 changed files with 88 additions and 295 deletions

View File

@ -4,7 +4,7 @@
The following dependencies are required for local development:
- [Go](https://golang.org/dl/) >= 1.15.6
- [Go](https://golang.org/dl/) >= 1.16.5
- [Docker](https://docs.docker.com/engine/install/) / moby-engine >= 19.03
- [Tilt](http://tilt.dev/) >= 0.20.8
- [NodeJS/npm](https://nodejs.org/en/download/) >= 14
@ -53,14 +53,6 @@ Restart a specific pod:
kubectl delete pod guardian-0
Generate test Ethereum -> Solana transfers once the cluster is up:
scripts/send-eth-lockups.sh
Generate test Solana -> Ethereum transfers:
scripts/send-solana-lockups.sh
Adjust number of nodes in running cluster: (this is only useful if you want to test scenarios where the number
of nodes diverges from the guardian set - otherwise, `tilt down --delete-namespaces` and restart the cluster)
@ -71,79 +63,3 @@ Tear down cluster:
tilt down --delete-namespaces
Once you're done, press Ctrl-C. Run `tilt down` to tear down the devnet.
## Web UI
The deployment includes a web UI that uses MetaMask to demonstrate token transfers. It's experimental
and meant as **example code on how to interact with Wormhole tokens** - the UI itself is just the bare minimum
to demonstrate how to use the libraries. The Tilt deployment automatically sets up port forwardings on your
local machine so you can access the devnet and the UI.
To access the UI, install the [MetaMask browser extension](https://metamask.io) and add a custom network with
RPC URL `http://localhost:8545` and chain ID `0x539`. Import the hardcoded Ganache seed phrase as account:
myth like bonus scare over problem client lizard pioneer submit female collect
You can now play with the web UI by initiating token transfers in either directions. The devnet comes with a number
of deterministic accounts on both chains that you can use (see below for copy&paste).
For example, send a bunch of SPL tokens to Ethereum:
<img src="https://user-images.githubusercontent.com/859697/98732005-dcbb4f00-239e-11eb-8ec2-9ecf74f411d6.png" width="66%" />
Note how the transfer is basically instant! You can now see the completed transfer in the Ethereum tab:
![image](https://user-images.githubusercontent.com/859697/98732459-784cbf80-239f-11eb-84bf-3d824b58191b.png)
You can now add the wrapped token address - `0xf5b1d8fab1054b9cf7db274126972f97f9d42a11` - as a custom token
to MetaMask and you'll see your 1000 brand new wrapped tokens:
<img src="https://user-images.githubusercontent.com/859697/98733991-a9c68a80-23a1-11eb-926b-ea3742fad7bd.png" width="66%" />
Next, send some of them back to Ethereum:
<img src="https://user-images.githubusercontent.com/859697/98734694-a1228400-23a2-11eb-8f39-13000631c839.png" width="66%" />
MetaMask will ask you to confirm the transaction.
After a few seconds, the SPL token balance shown below will increase as the VAA gets accepted on Solana.
## Devnet addresses
**Solana**
| Account | Description |
|------------------------------------------------|-----------------------------------------------------|
| `6sbzC1eH4FTujJXWj51eQe25cYvr4xfXbJ1vAj7j2k5J` | id.json account in the `setup` container [1] |
| `Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o` | Bridge contract |
| `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA` | SPL token contract |
| `6qRhs8oAuZYLd4zzaNnQHqdRyknrQQWDWQhALEN8UA7M` | Example SPL token |
| `3C3m4tjTy4nSMkkYdqCDSiCWEgpDa6whvprvABdFGBiW` | Account that holds 6qRhs8oA... SPL tokens |
| `85kW19uNvETzH43p3AfpyqPaQS5rWouq4x9rGiKUvihf` | Wrapped token for the 0xCfEB86... ERC20 token |
| `7EFk3VrWeb29SWJPQs5cUyqcY3fQd33S9gELkGybRzeu` | Account that holds 85kW19u... wrapped tokens [2] |
| `9ESkHLgJH4zqbG7fvhpC9u2ZeHMoLJznCHtaRLviEVRh` | Wrapped token for the terra18vd8f... CW20 token |
| `EERzaqe8Agm8p1ZkGQFq9zKpP7MDW29FX1pC1vEw9Yfv` | Account that holds 9ESkHLg... wrapped tokens |
[1]: The account will eventually run out of funds if you run the lockup sending scripts for a long time. Refill it
using `kubectl exec solana-devnet-0 -c setup -- cli airdrop solana-devnet:9900` (see [devnet_setup.sh](solana/devnet_setup.sh)).
[2]: This is where tokens sent by `scripts/send-eth-lockups.sh` end up.
**Ethereum**
| Account | Description |
|----------------------------------------------|-------------------------------------------------------|
| `0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1` | Ganache main account (w/ the seed phrase above) |
| `0x5b1869D9A4C187F2EAa108f3062412ecf0526b24` | Bridge contract |
| `0xe78A0F7E598Cc8b0Bb87894B0F60dD2a88d6a8Ab` | Wrapped asset contract |
| `0xCfEB869F69431e42cdB54A4F4f105C19C080A601` | Example ERC20 token |
| `0xf5b1d8fab1054b9cf7db274126972f97f9d42a11` | Wrapped asset address for the 6qRhs8oA... SPL token |
| `0x62b47a23cd900da982bdbe75aeb891d3ed18cc36` | Wrapped asset address for the terra18v... Terra token |
**Terra**
| Account | Description |
|------------------------------------------------|-----------------------------------------------------|
| `terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v` | Main test account |
| `terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5` | Test token account to send via bridge |
| `terra174kgn5rtw4kf6f938wm7kwh70h2v4vcfd26jlc` | Bridge contract instance |

View File

@ -1,5 +1,7 @@
# Wormhole v2
This repository contains Certus One's reference node implementation for the Wormhole project.
See [DEVELOP.md](DEVELOP.md) for instructions on how to set up a local devnet, and
[CONTRIBUTING.md](CONTRIBUTING.md) for instructions on how to contribute to this project.
@ -16,19 +18,3 @@ implied. See the License for the specific language governing permissions and lim
spoken - this is a very complex piece of software which targets a bleeding-edge, experimental smart contract runtime.
Mistakes happen, and no matter how hard you try and whether you pay someone to audit it, it may eat your tokens, set
your printer on fire or startle your cat. Cryptocurrencies are a high-risk investment, no matter how fancy.
### READ FIRST BEFORE USING WORMHOLE
- Much of the Solana ecosystem uses wrapped assets issued by a centralized bridge operated by FTX (the "Sollet bridge").
Markets on Serum or Raydium are using those centralized assets rather than Wormhole wrapped assets. These have names
like "Wrapped BTC" or "Wrapped ETH". Wormhole is going to replace the FTX bridge eventually, but this will take some
time - meanwhile, **Wormhole wrapped assets aren't terribly useful yet since there're no market for them.**
- Other tokens on Solana like USDC and USDT are **centralized native tokens issued on multiple chains**. If you transfer
USDT from Ethereum to Solana, you will get "Wormhole Wrapped USDT" (wwUSDT), rather than native USDT.
- **Solana's SPL tokens have no on-chain metadata**. Wormhole can't know the name of the token when you
transfer assets to Ethereum. All tokens are therefore named "WWT" plus the address of the SPL token.
The reverse is also true - Wormhole knows the name of the ERC20 token, but there's no way to store it on Solana.
There's an [off-chain name registry](https://github.com/solana-labs/token-list) that some block explorers use, but
if you transfer an uncommon token to Solana, it may not show with a name on block explorers.

View File

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

View File

@ -31,15 +31,15 @@ var (
Help: "Total number of Ethereum connection errors (either during initial connection or while watching)",
}, []string{"reason"})
ethLockupsFound = prometheus.NewCounter(
ethMessagesObserved = prometheus.NewCounter(
prometheus.CounterOpts{
Name: "wormhole_eth_lockups_found_total",
Help: "Total number of Eth lockups found (pre-confirmation)",
Name: "wormhole_eth_messages_observed_total",
Help: "Total number of Eth messages observed (pre-confirmation)",
})
ethLockupsConfirmed = prometheus.NewCounter(
ethMessagesConfirmed = prometheus.NewCounter(
prometheus.CounterOpts{
Name: "wormhole_eth_lockups_confirmed_total",
Help: "Total number of Eth lockups verified (post-confirmation)",
Name: "wormhole_eth_messages_confirmed_total",
Help: "Total number of Eth messages verified (post-confirmation)",
})
guardianSetChangesConfirmed = prometheus.NewCounter(
prometheus.CounterOpts{
@ -60,8 +60,8 @@ var (
func init() {
prometheus.MustRegister(ethConnectionErrors)
prometheus.MustRegister(ethLockupsFound)
prometheus.MustRegister(ethLockupsConfirmed)
prometheus.MustRegister(ethMessagesObserved)
prometheus.MustRegister(ethMessagesConfirmed)
prometheus.MustRegister(guardianSetChangesConfirmed)
prometheus.MustRegister(currentEthHeight)
prometheus.MustRegister(queryLatency)
@ -117,12 +117,12 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error {
timeout, cancel = context.WithTimeout(ctx, 15*time.Second)
defer cancel()
// Subscribe to new token lockups
tokensLockedC := make(chan *abi.AbiLogMessagePublished, 2)
tokensLockedSub, err := f.WatchLogMessagePublished(&bind.WatchOpts{Context: timeout}, tokensLockedC, nil)
// Subscribe to new message publications
messageC := make(chan *abi.AbiLogMessagePublished, 2)
messageSub, err := f.WatchLogMessagePublished(&bind.WatchOpts{Context: timeout}, messageC, nil)
if err != nil {
ethConnectionErrors.WithLabelValues("subscribe_error").Inc()
return fmt.Errorf("failed to subscribe to token lockup events: %w", err)
return fmt.Errorf("failed to subscribe to message publication events: %w", err)
}
// Subscribe to guardian set changes
@ -156,15 +156,15 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error {
select {
case <-ctx.Done():
return
case e := <-tokensLockedSub.Err():
case e := <-messageSub.Err():
ethConnectionErrors.WithLabelValues("subscription_error").Inc()
errC <- fmt.Errorf("error while processing token lockup subscription: %w", e)
errC <- fmt.Errorf("error while processing message publication subscription: %w", e)
return
case e := <-guardianSetEvent.Err():
ethConnectionErrors.WithLabelValues("subscription_error").Inc()
errC <- fmt.Errorf("error while processing guardian set subscription: %w", e)
return
case ev := <-tokensLockedC:
case ev := <-messageC:
// Request timestamp for block
msm := time.Now()
timeout, cancel = context.WithTimeout(ctx, 15*time.Second)
@ -189,10 +189,10 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error {
ConsistencyLevel: ev.ConsistencyLevel,
}
logger.Info("found new lockup transaction", zap.Stringer("tx", ev.Raw.TxHash),
logger.Info("found new message publication transaction", zap.Stringer("tx", ev.Raw.TxHash),
zap.Uint64("block", ev.Raw.BlockNumber))
ethLockupsFound.Inc()
ethMessagesObserved.Inc()
e.pendingLocksGuard.Lock()
e.pendingLocks[ev.Raw.TxHash] = &pendingLock{
@ -259,7 +259,7 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error {
// Transaction was dropped and never picked up again
if pLock.height+4*uint64(pLock.lock.ConsistencyLevel) <= blockNumberU {
logger.Debug("lockup timed out", zap.Stringer("tx", pLock.lock.TxHash),
logger.Debug("observation timed out", zap.Stringer("tx", pLock.lock.TxHash),
zap.Stringer("block", ev.Number))
delete(e.pendingLocks, hash)
continue
@ -267,11 +267,11 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error {
// Transaction is now ready
if pLock.height+uint64(pLock.lock.ConsistencyLevel) <= ev.Number.Uint64() {
logger.Debug("lockup confirmed", zap.Stringer("tx", pLock.lock.TxHash),
logger.Debug("observation confirmed", zap.Stringer("tx", pLock.lock.TxHash),
zap.Stringer("block", ev.Number))
delete(e.pendingLocks, hash)
e.lockChan <- pLock.lock
ethLockupsConfirmed.Inc()
ethMessagesConfirmed.Inc()
}
}

View File

@ -33,7 +33,7 @@ var (
aggregationStateUnobserved = prometheus.NewCounter(
prometheus.CounterOpts{
Name: "wormhole_aggregation_state_unobserved_total",
Help: "Total number of aggregation states expired due to no matching local lockup observations",
Help: "Total number of aggregation states expired due to no matching local message observations",
})
aggregationStateFulfillment = prometheus.NewCounterVec(
prometheus.CounterOpts{

View File

@ -17,29 +17,29 @@ var (
// SECURITY: source_chain/target_chain are untrusted uint8 values. An attacker could cause a maximum of 255**2 label
// pairs to be created, which is acceptable.
lockupsObservedTotal = prometheus.NewCounterVec(
messagesObservedTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "wormhole_lockups_observed_total",
Help: "Total number of lockups received on-chain",
Name: "wormhole_message_observations_total",
Help: "Total number of messages observed",
},
[]string{"emitter_chain"})
lockupsSignedTotal = prometheus.NewCounterVec(
messagesSignedTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "wormhole_lockups_signed_total",
Help: "Total number of lockups that were successfully signed",
Name: "wormhole_message_observations_signed_total",
Help: "Total number of message observations that were successfully signed",
},
[]string{"emitter_chain"})
)
func init() {
prometheus.MustRegister(lockupsObservedTotal)
prometheus.MustRegister(lockupsSignedTotal)
prometheus.MustRegister(messagesObservedTotal)
prometheus.MustRegister(messagesSignedTotal)
}
// handleLockup processes a lockup received from a chain and instantiates our deterministic copy of the VAA. A lockup
// event may be received multiple times until it has been successfully completed.
func (p *Processor) handleLockup(ctx context.Context, k *common.MessagePublication) {
// handleMessage processes a message received from a chain and instantiates our deterministic copy of the VAA. An
// event may be received multiple times and must be handled in an idempotent fashion.
func (p *Processor) handleMessage(ctx context.Context, k *common.MessagePublication) {
supervisor.Logger(ctx).Info("message publication confirmed",
zap.Stringer("emitter_chain", k.EmitterChain),
zap.Stringer("emitter_address", k.EmitterAddress),
@ -48,7 +48,7 @@ func (p *Processor) handleLockup(ctx context.Context, k *common.MessagePublicati
zap.Time("timestamp", k.Timestamp),
)
lockupsObservedTotal.With(prometheus.Labels{
messagesObservedTotal.With(prometheus.Labels{
"emitter_chain": k.EmitterChain.String(),
}).Add(1)
@ -86,7 +86,7 @@ func (p *Processor) handleLockup(ctx context.Context, k *common.MessagePublicati
zap.String("digest", hex.EncodeToString(digest.Bytes())),
zap.String("signature", hex.EncodeToString(s)))
lockupsSignedTotal.With(prometheus.Labels{
messagesSignedTotal.With(prometheus.Labels{
"emitter_chain": k.EmitterChain.String()}).Add(1)
p.broadcastSignature(v, s)

View File

@ -32,10 +32,10 @@ var (
Name: "wormhole_observations_verification_failures_total",
Help: "Total number of observations verification failure, grouped by failure reason",
}, []string{"cause"})
observationsUnknownLockupTotal = prometheus.NewCounter(
observationsUnknownTotal = prometheus.NewCounter(
prometheus.CounterOpts{
Name: "wormhole_observations_unknown_lockup_total",
Help: "Total number of verified VAA observations for a lockup we haven't seen yet",
Name: "wormhole_observations_unknown_total",
Help: "Total number of verified observations we haven't seen ourselves",
})
observationsDirectSubmissionsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
@ -53,7 +53,7 @@ func init() {
prometheus.MustRegister(observationsReceivedTotal)
prometheus.MustRegister(observationsReceivedByGuardianAddressTotal)
prometheus.MustRegister(observationsFailedTotal)
prometheus.MustRegister(observationsUnknownLockupTotal)
prometheus.MustRegister(observationsUnknownTotal)
prometheus.MustRegister(observationsDirectSubmissionsTotal)
prometheus.MustRegister(observationsDirectSubmissionSuccessTotal)
}
@ -104,16 +104,16 @@ func (p *Processor) handleObservation(ctx context.Context, m *gossipv1.SignedObs
// Determine which guardian set to use. The following cases are possible:
//
// - We have already seen the lockup and generated ourVAA. In this case, use the guardian set valid at the time,
// - We have already seen the message and generated ourVAA. In this case, use the guardian set valid at the time,
// even if the guardian set was updated. Old guardian sets remain valid for longer than aggregation state,
// and the guardians in the old set stay online and observe and sign lockup for the transition period.
// and the guardians in the old set stay online and observe and sign messages for the transition period.
//
// - We have not yet seen the lockup. In this case, we assume the latest guardian set because that's what
// we will store once we do see the lockup.
// - We have not yet seen the message. In this case, we assume the latest guardian set because that's what
// we will store once we do see the message.
//
// This ensures that during a guardian set update, a node which observed a given lockup with either the old
// This ensures that during a guardian set update, a node which observed a given message with either the old
// or the new guardian set can achieve consensus, since both the old and the new set would achieve consensus,
// assuming that 2/3+ of the old and the new guardian set have seen the lockup and will periodically attempt
// assuming that 2/3+ of the old and the new guardian set have seen the message and will periodically attempt
// to retransmit their observations such that nodes who initially dropped the signature will get a 2nd chance.
//
// During an update, vaaState.signatures can contain signatures from *both* guardian sets.
@ -163,11 +163,11 @@ func (p *Processor) handleObservation(ctx context.Context, m *gossipv1.SignedObs
// However, we have established that a valid guardian has signed it, and therefore we can
// already start aggregating signatures for it.
//
// A malicious guardian can potentially DoS this by creating fake lockups at a faster rate than they decay,
// A malicious guardian can potentially DoS this by creating fake observations at a faster rate than they decay,
// leading to a slow out-of-memory crash. We do not attempt to automatically mitigate spam attacks with valid
// signatures - such byzantine behavior would be plainly visible and would be dealt with by kicking them.
observationsUnknownLockupTotal.Inc()
observationsUnknownTotal.Inc()
p.state.vaaSignatures[hash] = &vaaState{
firstObserved: time.Now(),

View File

@ -18,9 +18,9 @@ import (
type (
// vaaState represents the local view of a given VAA
vaaState struct {
// First time this digest was seen (possibly even before we saw its lockup).
// First time this digest was seen (possibly even before we observed it ourselves).
firstObserved time.Time
// Copy of the VAA we constructed when we saw the lockup.
// Copy of the VAA we constructed when we made our own observation.
ourVAA *vaa.VAA
// Map of signatures seen by guardian. During guardian set updates, this may contain signatures belonging
// to either the old or new guardian set.
@ -35,7 +35,7 @@ type (
retryCount uint
// Copy of the bytes we submitted (ourVAA, but signed and serialized). Used for retransmissions.
ourMsg []byte
// Copy of the guardian set valid at lockup/injection time.
// Copy of the guardian set valid at observation/injection time.
gs *common.GuardianSet
}
@ -48,7 +48,7 @@ type (
)
type Processor struct {
// lockC is a channel of observed chain lockups
// lockC is a channel of observed emitted messages
lockC chan *common.MessagePublication
// setC is a channel of guardian set updates
setC chan *common.GuardianSet
@ -136,7 +136,7 @@ func (p *Processor) Run(ctx context.Context) error {
zap.Strings("set", p.gs.KeysAsHexStrings()),
zap.Uint32("index", p.gs.Index))
case k := <-p.lockC:
p.handleLockup(ctx, k)
p.handleMessage(ctx, k)
case v := <-p.injectC:
p.handleInjection(ctx, v)
case m := <-p.obsvC:

View File

@ -36,15 +36,15 @@ var (
Name: "wormhole_solana_account_updates_skipped_total",
Help: "Total number of account updates skipped due to invalid data",
}, []string{"reason"})
solanaLockupsConfirmed = prometheus.NewCounter(
solanaMessagesConfirmed = prometheus.NewCounter(
prometheus.CounterOpts{
Name: "wormhole_solana_lockups_confirmed_total",
Help: "Total number of verified Solana lockups found",
Name: "wormhole_solana_observations_confirmed_total",
Help: "Total number of verified Solana observations found",
})
currentSolanaHeight = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "wormhole_solana_current_height",
Help: "Current Solana slot height (at default commitment level, not the level used for lockups)",
Help: "Current Solana slot height (at default commitment level, not the level used for observations)",
})
queryLatency = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
@ -56,7 +56,7 @@ var (
func init() {
prometheus.MustRegister(solanaConnectionErrors)
prometheus.MustRegister(solanaAccountSkips)
prometheus.MustRegister(solanaLockupsConfirmed)
prometheus.MustRegister(solanaMessagesConfirmed)
prometheus.MustRegister(currentSolanaHeight)
prometheus.MustRegister(queryLatency)
}
@ -213,8 +213,8 @@ func (s *SolanaWatcher) Run(ctx context.Context) error {
ConsistencyLevel: proposal.ConsistencyLevel,
}
solanaLockupsConfirmed.Inc()
logger.Info("found lockup without VAA", zap.Stringer("lockup_address", acc.Pubkey))
solanaMessagesConfirmed.Inc()
logger.Info("found message account without VAA", zap.Stringer("address", acc.Pubkey))
s.messageEvent <- lock
}
}()

View File

@ -41,15 +41,15 @@ var (
Name: "wormhole_terra_connection_errors_total",
Help: "Total number of Terra connection errors",
}, []string{"reason"})
terraLockupsConfirmed = prometheus.NewCounter(
terraMessagesConfirmed = prometheus.NewCounter(
prometheus.CounterOpts{
Name: "wormhole_terra_lockups_confirmed_total",
Help: "Total number of verified terra lockups found",
Name: "wormhole_terra_messages_confirmed_total",
Help: "Total number of verified terra messages found",
})
currentTerraHeight = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "wormhole_terra_current_height",
Help: "Current terra slot height (at default commitment level, not the level used for lockups)",
Help: "Current terra slot height (at default commitment level, not the level used for observations)",
})
queryLatency = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
@ -60,7 +60,7 @@ var (
func init() {
prometheus.MustRegister(terraConnectionErrors)
prometheus.MustRegister(terraLockupsConfirmed)
prometheus.MustRegister(terraMessagesConfirmed)
prometheus.MustRegister(currentTerraHeight)
prometheus.MustRegister(queryLatency)
}
@ -219,7 +219,7 @@ func (e *BridgeWatcher) Run(ctx context.Context) error {
ConsistencyLevel: 0, // Instant finality
}
e.msgChan <- messagePublication
terraLockupsConfirmed.Inc()
terraMessagesConfirmed.Inc()
}
client := &http.Client{

View File

@ -624,28 +624,28 @@
"steppedLine": false,
"targets": [
{
"expr": "increase(wormhole_eth_lockups_found_total{instance=~\"$instance\"}[$__rate_interval])",
"expr": "increase(wormhole_eth_messages_observed_total{instance=~\"$instance\"}[$__rate_interval])",
"interval": "",
"legendFormat": "ethereum (found)",
"queryType": "randomWalk",
"refId": "A"
},
{
"expr": "increase(wormhole_eth_lockups_confirmed_total{instance=~\"$instance\"}[$__rate_interval])",
"expr": "increase(wormhole_eth_messages_confirmed_total{instance=~\"$instance\"}[$__rate_interval])",
"interval": "",
"legendFormat": "ethereum (confirmed)",
"queryType": "randomWalk",
"refId": "B"
},
{
"expr": "increase(wormhole_solana_lockups_confirmed_total{instance=~\"$instance\"}[$__rate_interval])",
"expr": "increase(wormhole_solana_observations_confirmed_total{instance=~\"$instance\"}[$__rate_interval])",
"interval": "",
"legendFormat": "solana (confirmed)",
"queryType": "randomWalk",
"refId": "C"
},
{
"expr": "increase(wormhole_terra_lockups_confirmed_total{instance=~\"$instance\"}[$__rate_interval])",
"expr": "increase(wormhole_terra_messages_confirmed_total{instance=~\"$instance\"}[$__rate_interval])",
"interval": "",
"legendFormat": "terra (confirmed)",
"queryType": "randomWalk",
@ -656,7 +656,7 @@
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Lockup Observations",
"title": "Observations",
"tooltip": {
"shared": true,
"sort": 0,
@ -1556,4 +1556,4 @@
"title": "Wormhole",
"uid": "CVGqeaYMz",
"version": 37
}
}

View File

@ -25,13 +25,16 @@ In addition to Wormhole itself, you need to run your own verifying node for ever
- **Binance Smart Chain**: Same requirements as Ethereum. Note that BSC has higher throughput than Ethereum and
roughly requires twice as many compute resources.
Do NOT use third-party RPC service providers for any of the chains! You'd fully trust them, and they could lie to you on
whether an event has actually been observed. The whole point of Wormhole is not to rely on centralized nodes!
**Do NOT use third-party RPC service providers** for any of the chains! You'd fully trust them, and they could lie to
you on whether an event has actually been observed. The whole point of Wormhole is not to rely on centralized nodes!
We strongly recommend running your own full nodes for both testnet and mainnet (where applicable)
so you can test changes for your mainnet full nodes and gain operational experience.
### Ethereum node requirements
In order to observe events on the Ethereum chain, you need access to an Ethereum RPC endpoint. We use geth, but for the
sake of diversity, you may want to run something that isn't geth.
In order to observe events on the Ethereum chain, you need access to an Ethereum RPC endpoint. The most common
choice is geth, but for the sake of diversity, you may want to run something that isn't geth.
With RPC providers such as Alchemy, Infura, etc. you trust those operators to provide you with untampered chain data and
have no way of verifying correctness. Therefore, Wormhole requires either an Ethereum full-node or a light-client. The
@ -50,13 +53,13 @@ frequency). Light clients have much lower hardware requirements.
For security reasons, we do not provide a pre-built binary. You need to check out the repo and build the
guardiand binary from source. A Git repo is much harder to tamper with than release binaries.
To build the Wormhole node, you need [Go](https://golang.org/dl/) >= 1.15.6.
To build the Wormhole node, you need [Go](https://golang.org/dl/) >= 1.16.5.
First, check out the version of the Wormhole repo that you want to deploy:
```bash
git clone https://github.com/certusone/wormhole && cd wormhole
git checkout v0.1.2
git checkout v2.0.x
```
Then, compile the release binary as an unprivileged build user:
@ -97,49 +100,8 @@ The key file includes a human-readable part which includes the public key hashes
We strongly recommend a separate user and systemd services for the Wormhole services.
Example systemd unit for `guardiand.service`, including the right capabilities and best-practice security mitigations:
```
# /etc/systemd/system/guardiand.service
[Unit]
Description=Wormhole Bridge guardian daemon
Documentation=https://github.com/certusone/wormhole
Requires=network.target
After=network.target
[Service]
User=wormhole
Group=wormhole
ExecStart=/usr/local/bin/guardiand bridge \
--bootstrap "<see launch repo>" \
--network "<see launch repo>" \
--ethContract <see launch repo> \
--nodeName "my-node-name" \
--nodeKey /path/to/your/node.key \
--bridgeKey /path/to/your/guardian.key \
--ethRPC ws://your-eth-node:8545 \
--adminSocket /run/guardiand/admin.socket \
--solanaBridgeAddress "<see launch repo>" \
--solanaRPC http://solana-host:8899 \
--solanaWS ws://solana-devnet:8900
RuntimeDirectory=guardiand
RuntimeDirectoryMode=700
RuntimeDirectoryPreserve=yes
PermissionsStartOnly=yes
PrivateTmp=yes
PrivateDevices=yes
SecureBits=keep-caps
AmbientCapabilities=CAP_IPC_LOCK
CapabilityBoundingSet=CAP_IPC_LOCK
NoNewPrivileges=yes
Restart=on-failure
RestartSec=5s
LimitNOFILE=65536
LimitMEMLOCK=infinity
[Install]
WantedBy=multi-user.target
```
See the separate [wormhole-networks](https://github.com/certusone/wormhole-networks) repository for examples
on how to set up the guardiand unit for a specific network.
You need to open port 8999/udp in your firewall for the P2P network. Nothing else has to be exposed externally.

View File

@ -53,7 +53,7 @@ message Heartbeat {
// that they observed a given event.
//
// Observations always result from an external, final event being observed.
// Examples are lockups in finalized blocks on a block or guardian set changes
// Examples are emitted messages in finalized blocks on a block or guardian set changes
// injected by node operators after reaching off-chain consensus.
//
// The event is uniquely identified by its hashed (tx_hash, nonce, values...) tuple.
@ -65,7 +65,7 @@ message Heartbeat {
message SignedObservation {
// Guardian pubkey as truncated eth address.
bytes addr = 1;
// The lockup's deterministic, unique hash.
// The observation's deterministic, unique hash.
bytes hash = 2;
// ECSDA signature of the hash using the node's guardian key.
bytes signature = 3;

View File

@ -27,7 +27,7 @@ message InjectGovernanceVAARequest {
// is critical for replay protection - a given event may only ever be observed with the same timestamp,
// otherwise, it may be possible to execute it multiple times.
//
// For lockups, the timestamp identifies the block that the lockup belongs to.
// For messages, the timestamp identifies the block that the message belongs to.
// For governance VAAs, guardians inject the VAA manually. Best practice is to pick a timestamp which roughly matches
// the timing of the off-chain ceremony used to achieve consensus. For guardian set updates, the actual on-chain

View File

@ -1,4 +0,0 @@
#!/usr/bin/env bash
set -e
kubectl exec -it -c tests eth-devnet-0 -- npx truffle exec src/send-lockups.js

View File

@ -1,4 +0,0 @@
#!/usr/bin/env bash
set -e
kubectl exec -it solana-devnet-0 -c setup ./lockups.sh

View File

@ -1,16 +0,0 @@
#!/usr/bin/env bash
# Generate test lockups on Solana to be executed on Ethereum
# Constants (hardcoded)
bridge_address=Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o
recipient_address=90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
chain_id_ethereum=2
# Generated by devnet_setup.sh with seed
account=3C3m4tjTy4nSMkkYdqCDSiCWEgpDa6whvprvABdFGBiW
token=6qRhs8oAuZYLd4zzaNnQHqdRyknrQQWDWQhALEN8UA7M
while : ; do
cli lock "$bridge_address" "$account" "$token" 1000000000 "$chain_id_ethereum" "$RANDOM" "$recipient_address"
sleep 5
done

View File

@ -1,50 +1,3 @@
# Terra Wormhole Contracts
The Wormhole Terra integration is developed and maintained by Everstake / @ysavchenko.
## Summary
To facilitate token exchange via the Wormhole bridge blockchains must provide a set of smart contracts to process bridge commands on chain. Here are such contracts for the Terra blockchain.
The first contract, `cw20-wrapped` is basically a `cw20-base` contract ([see here](https://github.com/CosmWasm/cosmwasm-plus/tree/master/contracts/cw20-base)) instantiated for every new token type issued on the blockchain by the bridge. And the second one, `wormhole` provides the bridge functionality itself:
- It locks tokens on the Terra blockchain when they are sent out to other blockchains
- It sends out wrapped or original tokens (depending on the blockchain origin of the token) to recipients when receiving tokens from the other blockchains
## Details
### `cw20-wrapped`
This contract mostly wraps functionality of the `cw20-base` contract with the following differences:
- It stores `WrappedAssetInfo` state with information about the source blockchain, asset address on this blockchain and the `wormhole` contract address
- Once initialized it calls the hook action specified in the initialization params (`init_hook` field). It is used to record newly instantiated contract's address in the `wormhole` contract
- Full mint authority is provided to the `wormhole` contract
### `wormhole`
This contract controls token transfers, minting and burning as well as maintaining the list of guardians: off-chain
entities identified by their public key hashes, majority of whom can issue commands to the contract.
`wormhole` bridge processes the following instructions.
#### `SubmitVAA`
Receives VAAs from the guardians (read about VAAs [here](../../docs/protocol.md)), verifies and processes them. In the current bridge implementation VAAs can trigger the following actions:
- Send token to the Terra recipient
- Update the list of guardians
Sending tokens to the Terra recipient is handled by the `vaa_transfer` method. For the native Terra tokens it simply transfers the corresponding amount from its balance. For the non-native tokens `wormhole` either mints the corresponding amount from the already deployed `cw20-wrapped` contract or deploys a new one with the mint amount in the initialization message.
#### `RegisterAssetHook`
Gets called from the `cw20-wrapped` constructor to record its address in the contract's directory of wrapped assets. It is used later to check whether the wrapped contract for the asset is already deployed on Terra blockchain or not.
#### `LockAssets`
Called to initiate token transfer from the Terra blockchain to other blockchains. Caller must provide allowance to the `wormhole` contract to spend tokens, then the contract either transfers (if it is a native token) or burns it (if it is a wrapped token from the different blockchain). Then the information is logged to be read by the guardians operating the bridge, which triggers sending VAAs to the destination blockchain.
#### `SetActive`
Safety feature to turn off the `wormhole` contract in the case of any issues found in production. Only the owner can send this message, once the contract is inactive it stops processing token transfer commands.