Add commitment level to VAAs
This allows requesting attestations for various commitment/confirmation levels. This is helpful for low-latency applications like Pyth. Change-Id: Ib49ace163365106b227613d2f66b787b3e5f5461
This commit is contained in:
parent
844a303b5b
commit
af4e29978d
|
@ -51,9 +51,8 @@ var (
|
||||||
bridgeKeyPath *string
|
bridgeKeyPath *string
|
||||||
solanaBridgeAddress *string
|
solanaBridgeAddress *string
|
||||||
|
|
||||||
ethRPC *string
|
ethRPC *string
|
||||||
ethContract *string
|
ethContract *string
|
||||||
ethConfirmations *uint64
|
|
||||||
|
|
||||||
terraSupport *bool
|
terraSupport *bool
|
||||||
terraWS *string
|
terraWS *string
|
||||||
|
@ -92,7 +91,6 @@ func init() {
|
||||||
|
|
||||||
ethRPC = BridgeCmd.Flags().String("ethRPC", "", "Ethereum RPC URL")
|
ethRPC = BridgeCmd.Flags().String("ethRPC", "", "Ethereum RPC URL")
|
||||||
ethContract = BridgeCmd.Flags().String("ethContract", "", "Ethereum bridge contract address")
|
ethContract = BridgeCmd.Flags().String("ethContract", "", "Ethereum bridge contract address")
|
||||||
ethConfirmations = BridgeCmd.Flags().Uint64("ethConfirmations", 15, "Ethereum confirmation count requirement")
|
|
||||||
|
|
||||||
terraSupport = BridgeCmd.Flags().Bool("terra", false, "Turn on support for Terra")
|
terraSupport = BridgeCmd.Flags().Bool("terra", false, "Turn on support for Terra")
|
||||||
terraWS = BridgeCmd.Flags().String("terraWS", "", "Path to terrad root for websocket connection")
|
terraWS = BridgeCmd.Flags().String("terraWS", "", "Path to terrad root for websocket connection")
|
||||||
|
@ -396,7 +394,7 @@ func runBridge(cmd *cobra.Command, args []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := supervisor.Run(ctx, "ethwatch",
|
if err := supervisor.Run(ctx, "ethwatch",
|
||||||
ethereum.NewEthBridgeWatcher(*ethRPC, ethContractAddr, *ethConfirmations, lockC, setC).Run); err != nil {
|
ethereum.NewEthBridgeWatcher(*ethRPC, ethContractAddr, lockC, setC).Run); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,10 +11,11 @@ type MessagePublication struct {
|
||||||
TxHash common.Hash // TODO: rename to identifier? on Solana, this isn't actually the tx hash
|
TxHash common.Hash // TODO: rename to identifier? on Solana, this isn't actually the tx hash
|
||||||
Timestamp time.Time
|
Timestamp time.Time
|
||||||
|
|
||||||
Nonce uint32
|
Nonce uint32
|
||||||
Sequence uint64
|
Sequence uint64
|
||||||
EmitterChain vaa.ChainID
|
ConsistencyLevel uint8
|
||||||
EmitterAddress vaa.Address
|
EmitterChain vaa.ChainID
|
||||||
Payload []byte
|
EmitterAddress vaa.Address
|
||||||
Persist bool
|
Payload []byte
|
||||||
|
Persist bool
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -69,9 +69,8 @@ func init() {
|
||||||
|
|
||||||
type (
|
type (
|
||||||
EthBridgeWatcher struct {
|
EthBridgeWatcher struct {
|
||||||
url string
|
url string
|
||||||
bridge eth_common.Address
|
bridge eth_common.Address
|
||||||
minConfirmations uint64
|
|
||||||
|
|
||||||
pendingLocks map[eth_common.Hash]*pendingLock
|
pendingLocks map[eth_common.Hash]*pendingLock
|
||||||
pendingLocksGuard sync.Mutex
|
pendingLocksGuard sync.Mutex
|
||||||
|
@ -86,8 +85,8 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewEthBridgeWatcher(url string, bridge eth_common.Address, minConfirmations uint64, lockEvents chan *common.MessagePublication, setEvents chan *common.GuardianSet) *EthBridgeWatcher {
|
func NewEthBridgeWatcher(url string, bridge eth_common.Address, lockEvents chan *common.MessagePublication, setEvents chan *common.GuardianSet) *EthBridgeWatcher {
|
||||||
return &EthBridgeWatcher{url: url, bridge: bridge, minConfirmations: minConfirmations, lockChan: lockEvents, setChan: setEvents, pendingLocks: map[eth_common.Hash]*pendingLock{}}
|
return &EthBridgeWatcher{url: url, bridge: bridge, lockChan: lockEvents, setChan: setEvents, pendingLocks: map[eth_common.Hash]*pendingLock{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *EthBridgeWatcher) Run(ctx context.Context) error {
|
func (e *EthBridgeWatcher) Run(ctx context.Context) error {
|
||||||
|
@ -180,14 +179,15 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
lock := &common.MessagePublication{
|
lock := &common.MessagePublication{
|
||||||
TxHash: ev.Raw.TxHash,
|
TxHash: ev.Raw.TxHash,
|
||||||
Timestamp: time.Unix(int64(b.Time()), 0),
|
Timestamp: time.Unix(int64(b.Time()), 0),
|
||||||
Nonce: ev.Nonce,
|
Nonce: ev.Nonce,
|
||||||
Sequence: ev.Sequence,
|
Sequence: ev.Sequence,
|
||||||
EmitterChain: vaa.ChainIDEthereum,
|
EmitterChain: vaa.ChainIDEthereum,
|
||||||
EmitterAddress: PadAddress(ev.Sender),
|
EmitterAddress: PadAddress(ev.Sender),
|
||||||
Payload: ev.Payload,
|
Payload: ev.Payload,
|
||||||
Persist: ev.PersistMessage,
|
Persist: ev.PersistMessage,
|
||||||
|
ConsistencyLevel: ev.Commitment,
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("found new lockup transaction", zap.Stringer("tx", ev.Raw.TxHash),
|
logger.Info("found new lockup transaction", zap.Stringer("tx", ev.Raw.TxHash),
|
||||||
|
@ -259,7 +259,7 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error {
|
||||||
for hash, pLock := range e.pendingLocks {
|
for hash, pLock := range e.pendingLocks {
|
||||||
|
|
||||||
// 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*uint64(pLock.lock.ConsistencyLevel) <= blockNumberU {
|
||||||
logger.Debug("lockup timed out", zap.Stringer("tx", pLock.lock.TxHash),
|
logger.Debug("lockup timed out", zap.Stringer("tx", pLock.lock.TxHash),
|
||||||
zap.Stringer("block", ev.Number))
|
zap.Stringer("block", ev.Number))
|
||||||
delete(e.pendingLocks, hash)
|
delete(e.pendingLocks, hash)
|
||||||
|
@ -267,7 +267,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+uint64(pLock.lock.ConsistencyLevel) <= ev.Number.Uint64() {
|
||||||
logger.Debug("lockup confirmed", zap.Stringer("tx", pLock.lock.TxHash),
|
logger.Debug("lockup confirmed", zap.Stringer("tx", pLock.lock.TxHash),
|
||||||
zap.Stringer("block", ev.Number))
|
zap.Stringer("block", ev.Number))
|
||||||
delete(e.pendingLocks, hash)
|
delete(e.pendingLocks, hash)
|
||||||
|
|
|
@ -65,6 +65,7 @@ func (p *Processor) handleLockup(ctx context.Context, k *common.MessagePublicati
|
||||||
EmitterAddress: k.EmitterAddress,
|
EmitterAddress: k.EmitterAddress,
|
||||||
Payload: k.Payload,
|
Payload: k.Payload,
|
||||||
Sequence: k.Sequence,
|
Sequence: k.Sequence,
|
||||||
|
ConsistencyLevel: k.ConsistencyLevel,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate digest of the unsigned VAA.
|
// Generate digest of the unsigned VAA.
|
||||||
|
|
|
@ -213,6 +213,7 @@ func (p *Processor) handleObservation(ctx context.Context, m *gossipv1.SignedObs
|
||||||
EmitterChain: v.EmitterChain,
|
EmitterChain: v.EmitterChain,
|
||||||
EmitterAddress: v.EmitterAddress,
|
EmitterAddress: v.EmitterAddress,
|
||||||
Payload: v.Payload,
|
Payload: v.Payload,
|
||||||
|
ConsistencyLevel: v.ConsistencyLevel,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2/3+ majority required for VAA to be valid - wait until we have quorum to submit VAA.
|
// 2/3+ majority required for VAA to be valid - wait until we have quorum to submit VAA.
|
||||||
|
|
|
@ -49,7 +49,7 @@ var (
|
||||||
prometheus.HistogramOpts{
|
prometheus.HistogramOpts{
|
||||||
Name: "wormhole_solana_query_latency",
|
Name: "wormhole_solana_query_latency",
|
||||||
Help: "Latency histogram for Solana RPC calls",
|
Help: "Latency histogram for Solana RPC calls",
|
||||||
}, []string{"operation"})
|
}, []string{"operation", "commitment"})
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -90,7 +90,7 @@ func (s *SolanaWatcher) Run(ctx context.Context) error {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
slot, err := rpcClient.GetSlot(rCtx, "")
|
slot, err := rpcClient.GetSlot(rCtx, "")
|
||||||
queryLatency.WithLabelValues("get_slot").Observe(time.Since(start).Seconds())
|
queryLatency.WithLabelValues("get_slot", "processed").Observe(time.Since(start).Seconds())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
solanaConnectionErrors.WithLabelValues("get_slot_error").Inc()
|
solanaConnectionErrors.WithLabelValues("get_slot_error").Inc()
|
||||||
errC <- err
|
errC <- err
|
||||||
|
@ -109,7 +109,8 @@ func (s *SolanaWatcher) Run(ctx context.Context) error {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
|
|
||||||
accounts, err := rpcClient.GetProgramAccounts(rCtx, s.bridge, &rpc.GetProgramAccountsOpts{
|
// Get finalized accounts
|
||||||
|
fAccounts, err := rpcClient.GetProgramAccounts(rCtx, s.bridge, &rpc.GetProgramAccountsOpts{
|
||||||
Commitment: rpc.CommitmentMax, // TODO: deprecated, use Finalized
|
Commitment: rpc.CommitmentMax, // TODO: deprecated, use Finalized
|
||||||
Filters: []rpc.RPCFilter{
|
Filters: []rpc.RPCFilter{
|
||||||
{
|
{
|
||||||
|
@ -126,19 +127,65 @@ func (s *SolanaWatcher) Run(ctx context.Context) error {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Memcmp: &rpc.RPCFilterMemcmp{
|
Memcmp: &rpc.RPCFilterMemcmp{
|
||||||
Offset: 5, // Offset of VaaTime
|
Offset: 5, // Start of the ConsistencyLevel value
|
||||||
|
Bytes: solana.Base58{32}, // Only grab messages that require max confirmations
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Memcmp: &rpc.RPCFilterMemcmp{
|
||||||
|
Offset: 6, // Offset of VaaTime
|
||||||
Bytes: solana.Base58{0, 0, 0, 0}, // This means this VAA hasn't been signed yet
|
Bytes: solana.Base58{0, 0, 0, 0}, // This means this VAA hasn't been signed yet
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
queryLatency.WithLabelValues("get_program_accounts").Observe(time.Since(start).Seconds())
|
queryLatency.WithLabelValues("get_program_accounts", "max").Observe(time.Since(start).Seconds())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
solanaConnectionErrors.WithLabelValues("get_program_account_error").Inc()
|
solanaConnectionErrors.WithLabelValues("get_program_account_error").Inc()
|
||||||
errC <- err
|
errC <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get confirmed accounts
|
||||||
|
cAccounts, err := rpcClient.GetProgramAccounts(rCtx, s.bridge, &rpc.GetProgramAccountsOpts{
|
||||||
|
Commitment: rpc.CommitmentSingle, // TODO: deprecated, use Confirmed
|
||||||
|
Filters: []rpc.RPCFilter{
|
||||||
|
{
|
||||||
|
Memcmp: &rpc.RPCFilterMemcmp{
|
||||||
|
Offset: 0, // Start of the account
|
||||||
|
Bytes: solana.Base58{'m', 's', 'g'}, // Prefix of the posted message accounts
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Memcmp: &rpc.RPCFilterMemcmp{
|
||||||
|
Offset: 4, // Start of the Persist flag
|
||||||
|
Bytes: solana.Base58{0x01}, // Only grab messages that need to be persisted
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Memcmp: &rpc.RPCFilterMemcmp{
|
||||||
|
Offset: 5, // Start of the ConsistencyLevel value
|
||||||
|
Bytes: solana.Base58{1}, // Only grab messages that require the Confirmed level
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Memcmp: &rpc.RPCFilterMemcmp{
|
||||||
|
Offset: 6, // Offset of VaaTime
|
||||||
|
Bytes: solana.Base58{0, 0, 0, 0}, // This means this VAA hasn't been signed yet
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
queryLatency.WithLabelValues("get_program_accounts", "single").Observe(time.Since(start).Seconds())
|
||||||
|
if err != nil {
|
||||||
|
solanaConnectionErrors.WithLabelValues("get_program_account_error").Inc()
|
||||||
|
errC <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge accounts
|
||||||
|
accounts := append(fAccounts, cAccounts...)
|
||||||
|
|
||||||
logger.Debug("fetched transfer proposals without VAA",
|
logger.Debug("fetched transfer proposals without VAA",
|
||||||
zap.Int("n", len(accounts)),
|
zap.Int("n", len(accounts)),
|
||||||
zap.Duration("took", time.Since(start)),
|
zap.Duration("took", time.Since(start)),
|
||||||
|
@ -166,14 +213,15 @@ func (s *SolanaWatcher) Run(ctx context.Context) error {
|
||||||
copy(txHash[:], acc.Pubkey[:])
|
copy(txHash[:], acc.Pubkey[:])
|
||||||
|
|
||||||
lock := &common.MessagePublication{
|
lock := &common.MessagePublication{
|
||||||
TxHash: txHash,
|
TxHash: txHash,
|
||||||
Timestamp: time.Unix(int64(proposal.SubmissionTime), 0),
|
Timestamp: time.Unix(int64(proposal.SubmissionTime), 0),
|
||||||
Nonce: proposal.Nonce,
|
Nonce: proposal.Nonce,
|
||||||
Sequence: proposal.Sequence,
|
Sequence: proposal.Sequence,
|
||||||
EmitterChain: vaa.ChainIDSolana,
|
EmitterChain: vaa.ChainIDSolana,
|
||||||
EmitterAddress: proposal.EmitterAddress,
|
EmitterAddress: proposal.EmitterAddress,
|
||||||
Payload: proposal.Payload,
|
Payload: proposal.Payload,
|
||||||
Persist: proposal.Persist == 1,
|
Persist: proposal.Persist == 1,
|
||||||
|
ConsistencyLevel: proposal.ConsistencyLevel,
|
||||||
}
|
}
|
||||||
|
|
||||||
solanaLockupsConfirmed.Inc()
|
solanaLockupsConfirmed.Inc()
|
||||||
|
@ -198,6 +246,7 @@ type (
|
||||||
VaaVersion uint8
|
VaaVersion uint8
|
||||||
// Borsh does not seem to support booleans, so 0=false / 1=true
|
// Borsh does not seem to support booleans, so 0=false / 1=true
|
||||||
Persist uint8
|
Persist uint8
|
||||||
|
ConsistencyLevel uint8
|
||||||
VaaTime uint32
|
VaaTime uint32
|
||||||
VaaSignatureAccount vaa.Address
|
VaaSignatureAccount vaa.Address
|
||||||
SubmissionTime uint32
|
SubmissionTime uint32
|
||||||
|
|
|
@ -132,6 +132,7 @@ func (e *SolanaVAASubmitter) Run(ctx context.Context) error {
|
||||||
Payload: v.Payload,
|
Payload: v.Payload,
|
||||||
GuardianSetIndex: v.GuardianSetIndex,
|
GuardianSetIndex: v.GuardianSetIndex,
|
||||||
Signatures: signatures,
|
Signatures: signatures,
|
||||||
|
ConsistencyLevel: uint32(v.ConsistencyLevel),
|
||||||
}, SkipPreflight: e.skipPreflight})
|
}, SkipPreflight: e.skipPreflight})
|
||||||
cancel()
|
cancel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -211,14 +211,15 @@ func (e *BridgeWatcher) Run(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
messagePublication := &common.MessagePublication{
|
messagePublication := &common.MessagePublication{
|
||||||
TxHash: txHashValue,
|
TxHash: txHashValue,
|
||||||
Timestamp: time.Unix(blockTime.Int(), 0),
|
Timestamp: time.Unix(blockTime.Int(), 0),
|
||||||
Nonce: uint32(nonce.Uint()),
|
Nonce: uint32(nonce.Uint()),
|
||||||
Sequence: sequence.Uint(),
|
Sequence: sequence.Uint(),
|
||||||
EmitterChain: vaa.ChainIDTerra,
|
EmitterChain: vaa.ChainIDTerra,
|
||||||
EmitterAddress: senderAddress,
|
EmitterAddress: senderAddress,
|
||||||
Payload: payloadValue,
|
Payload: payloadValue,
|
||||||
Persist: persist.Bool(),
|
Persist: persist.Bool(),
|
||||||
|
ConsistencyLevel: 0, // Instant finality
|
||||||
}
|
}
|
||||||
e.msgChan <- messagePublication
|
e.msgChan <- messagePublication
|
||||||
terraLockupsConfirmed.Inc()
|
terraLockupsConfirmed.Inc()
|
||||||
|
|
|
@ -29,6 +29,8 @@ type (
|
||||||
Nonce uint32
|
Nonce uint32
|
||||||
// Sequence of the VAA
|
// Sequence of the VAA
|
||||||
Sequence uint64
|
Sequence uint64
|
||||||
|
/// ConsistencyLevel of the VAA
|
||||||
|
ConsistencyLevel uint8
|
||||||
// EmitterChain the VAA was emitted on
|
// EmitterChain the VAA was emitted on
|
||||||
EmitterChain ChainID
|
EmitterChain ChainID
|
||||||
// EmitterAddress of the contract that emitted the Message
|
// EmitterAddress of the contract that emitted the Message
|
||||||
|
@ -143,6 +145,15 @@ func Unmarshal(data []byte) (*VAA, error) {
|
||||||
if n, err := reader.Read(emitterAddress[:]); err != nil || n != 32 {
|
if n, err := reader.Read(emitterAddress[:]); err != nil || n != 32 {
|
||||||
return nil, fmt.Errorf("failed to read emitter address [%d]: %w", n, err)
|
return nil, fmt.Errorf("failed to read emitter address [%d]: %w", n, err)
|
||||||
}
|
}
|
||||||
|
v.EmitterAddress = emitterAddress
|
||||||
|
|
||||||
|
if err := binary.Read(reader, binary.BigEndian, &v.Sequence); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read sequence: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := binary.Read(reader, binary.BigEndian, &v.ConsistencyLevel); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read commitment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
payload := make([]byte, 1000)
|
payload := make([]byte, 1000)
|
||||||
n, err := reader.Read(payload)
|
n, err := reader.Read(payload)
|
||||||
|
@ -231,6 +242,7 @@ func (v *VAA) serializeBody() ([]byte, error) {
|
||||||
MustWrite(buf, binary.BigEndian, v.EmitterChain)
|
MustWrite(buf, binary.BigEndian, v.EmitterChain)
|
||||||
buf.Write(v.EmitterAddress[:])
|
buf.Write(v.EmitterAddress[:])
|
||||||
MustWrite(buf, binary.BigEndian, v.Sequence)
|
MustWrite(buf, binary.BigEndian, v.Sequence)
|
||||||
|
MustWrite(buf, binary.BigEndian, v.ConsistencyLevel)
|
||||||
buf.Write(v.Payload)
|
buf.Write(v.Payload)
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"math/big"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -18,7 +17,7 @@ func TestSerializeDeserialize(t *testing.T) {
|
||||||
vaa *VAA
|
vaa *VAA
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "BodyTransfer",
|
name: "NormalVAA",
|
||||||
vaa: &VAA{
|
vaa: &VAA{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
GuardianSetIndex: 9,
|
GuardianSetIndex: 9,
|
||||||
|
@ -28,55 +27,13 @@ func TestSerializeDeserialize(t *testing.T) {
|
||||||
Signature: [65]byte{},
|
Signature: [65]byte{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Timestamp: time.Unix(2837, 0),
|
Timestamp: time.Unix(2837, 0),
|
||||||
Payload: &BodyTransfer{
|
Nonce: 10,
|
||||||
Nonce: 38,
|
Sequence: 3,
|
||||||
SourceChain: 2,
|
ConsistencyLevel: 5,
|
||||||
TargetChain: 1,
|
EmitterChain: 8,
|
||||||
SourceAddress: Address{2, 1, 4},
|
EmitterAddress: Address{1, 2, 3},
|
||||||
TargetAddress: Address{2, 1, 3},
|
Payload: []byte("abc"),
|
||||||
Asset: &AssetMeta{
|
|
||||||
Chain: 9,
|
|
||||||
Address: Address{9, 2, 4},
|
|
||||||
},
|
|
||||||
Amount: big.NewInt(29),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "GuardianSetUpdate",
|
|
||||||
vaa: &VAA{
|
|
||||||
Version: 1,
|
|
||||||
GuardianSetIndex: 9,
|
|
||||||
Signatures: []*Signature{
|
|
||||||
{
|
|
||||||
Index: 1,
|
|
||||||
Signature: [65]byte{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Timestamp: time.Unix(2837, 0),
|
|
||||||
Payload: &BodyGuardianSetUpdate{
|
|
||||||
Keys: []common.Address{{}, {}},
|
|
||||||
NewIndex: 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ContractUpgrade",
|
|
||||||
vaa: &VAA{
|
|
||||||
Version: 1,
|
|
||||||
GuardianSetIndex: 9,
|
|
||||||
Signatures: []*Signature{
|
|
||||||
{
|
|
||||||
Index: 1,
|
|
||||||
Signature: [65]byte{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Timestamp: time.Unix(2837, 0),
|
|
||||||
Payload: &BodyContractUpgrade{
|
|
||||||
ChainID: ChainIDEthereum,
|
|
||||||
NewContract: Address{1, 3, 4, 5, 2, 3},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -100,16 +57,12 @@ func TestVerifySignature(t *testing.T) {
|
||||||
Version: 8,
|
Version: 8,
|
||||||
GuardianSetIndex: 9,
|
GuardianSetIndex: 9,
|
||||||
Timestamp: time.Unix(2837, 0),
|
Timestamp: time.Unix(2837, 0),
|
||||||
Payload: &BodyTransfer{
|
Nonce: 5,
|
||||||
SourceChain: 2,
|
Sequence: 10,
|
||||||
TargetChain: 1,
|
ConsistencyLevel: 2,
|
||||||
TargetAddress: Address{2, 1, 3},
|
EmitterChain: 2,
|
||||||
Asset: &AssetMeta{
|
EmitterAddress: Address{0, 1, 2, 3, 4},
|
||||||
Chain: 9,
|
Payload: []byte("abcd"),
|
||||||
Address: Address{9, 2, 4},
|
|
||||||
},
|
|
||||||
Amount: big.NewInt(29),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := v.SigningMsg()
|
data, err := v.SigningMsg()
|
||||||
|
|
|
@ -133,6 +133,13 @@ VAA struct {
|
||||||
// Tracked per (EmitterChain, EmitterAddress) tuple.
|
// Tracked per (EmitterChain, EmitterAddress) tuple.
|
||||||
Sequence uint64 // <-- NEW
|
Sequence uint64 // <-- NEW
|
||||||
|
|
||||||
|
// Level of consistency requested by the emitter.
|
||||||
|
//
|
||||||
|
// The semantic meaning of this field is specific to the target
|
||||||
|
// chain (like a commitment level on Solana, number of
|
||||||
|
// confirmations on Ethereum, or no meaning with instant finality).
|
||||||
|
ConsistencyLevel uint8 // <-- NEW
|
||||||
|
|
||||||
// Payload of the message.
|
// Payload of the message.
|
||||||
Payload []byte // <-- NEW
|
Payload []byte // <-- NEW
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,10 @@ message submitted.
|
||||||
The timestamp is derived by the guardian software using the finalized timestamp of the block the message was published
|
The timestamp is derived by the guardian software using the finalized timestamp of the block the message was published
|
||||||
in.
|
in.
|
||||||
|
|
||||||
|
When a message is posted, the emitter can specify for how many confirmations the guardians should wait before an
|
||||||
|
attestation is produced. This allows latency sensitive applications to make sacrifices on safety while critical
|
||||||
|
applications can sacrifice latency over safety. Chains with instant finality can omit the argument.
|
||||||
|
|
||||||
**Persistence:**
|
**Persistence:**
|
||||||
|
|
||||||
In case an emitter chooses to persist a message, the guardian software will publish it to the Solana blockchain where
|
In case an emitter chooses to persist a message, the guardian software will publish it to the Solana blockchain where
|
||||||
|
@ -89,7 +93,7 @@ bridge tokens back to the chain where the governance and staking contracts are l
|
||||||
|
|
||||||
Proposed bridge interface:
|
Proposed bridge interface:
|
||||||
|
|
||||||
`postMessage(bytes payload, bool persist)` - Publish a message to be attested by Wormhole.
|
`postMessage(bytes payload, bool persist, u8 confirmations)` - Publish a message to be attested by Wormhole.
|
||||||
|
|
||||||
`postVAA(VAA signed_vaa)` - Persist a VAA on chain (Solana only)
|
`postVAA(VAA signed_vaa)` - Persist a VAA on chain (Solana only)
|
||||||
|
|
||||||
|
|
|
@ -76,8 +76,6 @@ spec:
|
||||||
- /tmp/terra.key
|
- /tmp/terra.key
|
||||||
- --agentRPC
|
- --agentRPC
|
||||||
- /run/bridge/agent.sock
|
- /run/bridge/agent.sock
|
||||||
- --ethConfirmations
|
|
||||||
- '2'
|
|
||||||
- --solanaBridgeAddress
|
- --solanaBridgeAddress
|
||||||
- Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o
|
- Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o
|
||||||
- --solanaWS
|
- --solanaWS
|
||||||
|
|
|
@ -9,13 +9,14 @@ import "./Governance.sol";
|
||||||
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol";
|
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol";
|
||||||
|
|
||||||
contract Implementation is Governance {
|
contract Implementation is Governance {
|
||||||
event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, bool persistMessage);
|
event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, bool persistMessage, uint8 consistency_level);
|
||||||
|
|
||||||
// Publish a message to be attested by the Wormhole network
|
// Publish a message to be attested by the Wormhole network
|
||||||
function publishMessage(
|
function publishMessage(
|
||||||
uint32 nonce,
|
uint32 nonce,
|
||||||
bytes memory payload,
|
bytes memory payload,
|
||||||
bool persistMessage
|
bool persistMessage,
|
||||||
|
uint8 consistency_level
|
||||||
) public payable {
|
) public payable {
|
||||||
// check fee
|
// check fee
|
||||||
if( persistMessage ) {
|
if( persistMessage ) {
|
||||||
|
@ -25,7 +26,7 @@ contract Implementation is Governance {
|
||||||
}
|
}
|
||||||
|
|
||||||
// emit log
|
// emit log
|
||||||
emit LogMessagePublished(msg.sender, useSequence(msg.sender), nonce, payload, persistMessage);
|
emit LogMessagePublished(msg.sender, useSequence(msg.sender), nonce, payload, persistMessage, consistency_level);
|
||||||
}
|
}
|
||||||
|
|
||||||
function useSequence(address emitter) internal returns (uint64 sequence) {
|
function useSequence(address emitter) internal returns (uint64 sequence) {
|
||||||
|
|
|
@ -101,6 +101,9 @@ contract Messages is Getters {
|
||||||
vm.sequence = encodedVM.toUint64(index);
|
vm.sequence = encodedVM.toUint64(index);
|
||||||
index += 8;
|
index += 8;
|
||||||
|
|
||||||
|
vm.consistencyLevel = encodedVM.toUint8(index);
|
||||||
|
index += 1;
|
||||||
|
|
||||||
vm.payload = encodedVM.slice(index, encodedVM.length - index);
|
vm.payload = encodedVM.slice(index, encodedVM.length - index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ interface Structs {
|
||||||
uint16 emitterChainId;
|
uint16 emitterChainId;
|
||||||
bytes32 emitterAddress;
|
bytes32 emitterAddress;
|
||||||
uint64 sequence;
|
uint64 sequence;
|
||||||
|
uint8 consistencyLevel;
|
||||||
bytes payload;
|
bytes payload;
|
||||||
|
|
||||||
uint32 guardianSetIndex;
|
uint32 guardianSetIndex;
|
||||||
|
|
|
@ -27,9 +27,10 @@ message VAA {
|
||||||
uint32 EmitterChain = 4;
|
uint32 EmitterChain = 4;
|
||||||
bytes EmitterAddress = 5;
|
bytes EmitterAddress = 5;
|
||||||
uint64 Sequence = 6;
|
uint64 Sequence = 6;
|
||||||
bytes Payload = 7;
|
uint32 ConsistencyLevel = 7;
|
||||||
uint32 GuardianSetIndex = 8;
|
bytes Payload = 8;
|
||||||
repeated Signature Signatures = 9;
|
uint32 GuardianSetIndex = 9;
|
||||||
|
repeated Signature Signatures = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Signature{
|
message Signature{
|
||||||
|
|
|
@ -125,6 +125,7 @@ impl Agent for AgentImpl {
|
||||||
emitter_chain: vaa.emitter_chain as u16,
|
emitter_chain: vaa.emitter_chain as u16,
|
||||||
emitter_address: emitter_address,
|
emitter_address: emitter_address,
|
||||||
sequence: vaa.sequence,
|
sequence: vaa.sequence,
|
||||||
|
consistency_level: vaa.consistency_level as u8,
|
||||||
payload: vaa.payload.clone(),
|
payload: vaa.payload.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -114,6 +114,7 @@ fn command_post_message(
|
||||||
nonce: u32,
|
nonce: u32,
|
||||||
payload: Vec<u8>,
|
payload: Vec<u8>,
|
||||||
persist: bool,
|
persist: bool,
|
||||||
|
commitment: bridge::types::ConsistencyLevel,
|
||||||
) -> CommmandResult {
|
) -> CommmandResult {
|
||||||
println!("Posting a message to the wormhole");
|
println!("Posting a message to the wormhole");
|
||||||
|
|
||||||
|
@ -138,13 +139,16 @@ fn command_post_message(
|
||||||
&FeeCollector::key(None, bridge),
|
&FeeCollector::key(None, bridge),
|
||||||
fee,
|
fee,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let emitter = Keypair::new();
|
||||||
let (_, ix) = bridge::instructions::post_message(
|
let (_, ix) = bridge::instructions::post_message(
|
||||||
*bridge,
|
*bridge,
|
||||||
config.owner.pubkey(),
|
config.owner.pubkey(),
|
||||||
config.fee_payer.pubkey(),
|
emitter.pubkey(),
|
||||||
nonce,
|
nonce,
|
||||||
payload,
|
payload,
|
||||||
persist,
|
persist,
|
||||||
|
commitment,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut transaction =
|
let mut transaction =
|
||||||
|
@ -152,7 +156,10 @@ fn command_post_message(
|
||||||
|
|
||||||
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||||
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
|
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
|
||||||
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
transaction.sign(
|
||||||
|
&[&config.fee_payer, &config.owner, &emitter],
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
Ok(Some(transaction))
|
Ok(Some(transaction))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,12 +286,20 @@ fn main() {
|
||||||
.required(true)
|
.required(true)
|
||||||
.help("Nonce of the message"),
|
.help("Nonce of the message"),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("consistency_level")
|
||||||
|
.value_name("CONSISTENCY_LEVEL")
|
||||||
|
.takes_value(true)
|
||||||
|
.index(3)
|
||||||
|
.required(true)
|
||||||
|
.help("Consistency (Commitment) level at which the VAA should be produced <FINALIZED|CONFIRMED>"),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("data")
|
Arg::with_name("data")
|
||||||
.validator(is_hex)
|
.validator(is_hex)
|
||||||
.value_name("DATA")
|
.value_name("DATA")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.index(3)
|
.index(4)
|
||||||
.required(true)
|
.required(true)
|
||||||
.help("Payload of the message"),
|
.help("Payload of the message"),
|
||||||
)
|
)
|
||||||
|
@ -352,8 +367,23 @@ fn main() {
|
||||||
let data = hex::decode(data_str).unwrap();
|
let data = hex::decode(data_str).unwrap();
|
||||||
let nonce: u32 = value_of(arg_matches, "nonce").unwrap();
|
let nonce: u32 = value_of(arg_matches, "nonce").unwrap();
|
||||||
let persist = arg_matches.is_present("persist");
|
let persist = arg_matches.is_present("persist");
|
||||||
|
let consistency_level: String = value_of(arg_matches, "consistency_level").unwrap();
|
||||||
|
|
||||||
command_post_message(&config, &bridge, nonce, data, persist)
|
command_post_message(
|
||||||
|
&config,
|
||||||
|
&bridge,
|
||||||
|
nonce,
|
||||||
|
data,
|
||||||
|
persist,
|
||||||
|
match consistency_level.to_lowercase().as_str() {
|
||||||
|
"finalized" => bridge::types::ConsistencyLevel::Finalized,
|
||||||
|
"confirmed" => bridge::types::ConsistencyLevel::Confirmed,
|
||||||
|
_ => {
|
||||||
|
eprintln!("Invalid commitment level");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
|
|
@ -17,7 +17,7 @@ use solitaire::{
|
||||||
|
|
||||||
type Payer<'a> = Signer<Info<'a>>;
|
type Payer<'a> = Signer<Info<'a>>;
|
||||||
|
|
||||||
#[derive(FromAccounts, ToInstruction)]
|
#[derive(FromAccounts)]
|
||||||
pub struct Initialize<'b> {
|
pub struct Initialize<'b> {
|
||||||
/// Bridge config.
|
/// Bridge config.
|
||||||
pub bridge: Mut<Bridge<'b, { AccountState::Uninitialized }>>,
|
pub bridge: Mut<Bridge<'b, { AccountState::Uninitialized }>>,
|
||||||
|
|
|
@ -11,6 +11,7 @@ use crate::{
|
||||||
InsufficientFees,
|
InsufficientFees,
|
||||||
MathOverflow,
|
MathOverflow,
|
||||||
},
|
},
|
||||||
|
types::ConsistencyLevel,
|
||||||
CHAIN_ID_SOLANA,
|
CHAIN_ID_SOLANA,
|
||||||
};
|
};
|
||||||
use solana_program::{
|
use solana_program::{
|
||||||
|
@ -64,7 +65,7 @@ impl<'b> InstructionContext<'b> for PostMessage<'b> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(BorshDeserialize, BorshSerialize, Default)]
|
#[derive(BorshDeserialize, BorshSerialize)]
|
||||||
pub struct PostMessageData {
|
pub struct PostMessageData {
|
||||||
/// Unique nonce for this message
|
/// Unique nonce for this message
|
||||||
pub nonce: u32,
|
pub nonce: u32,
|
||||||
|
@ -74,6 +75,9 @@ pub struct PostMessageData {
|
||||||
|
|
||||||
/// Should the VAA for this message be persisted on-chain
|
/// Should the VAA for this message be persisted on-chain
|
||||||
pub persist: bool,
|
pub persist: bool,
|
||||||
|
|
||||||
|
/// Commitment Level required for an attestation to be produced
|
||||||
|
pub consistency_level: ConsistencyLevel,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn post_message(
|
pub fn post_message(
|
||||||
|
@ -138,6 +142,10 @@ pub fn post_message(
|
||||||
accs.message.payload = data.payload;
|
accs.message.payload = data.payload;
|
||||||
accs.message.sequence = accs.sequence.sequence;
|
accs.message.sequence = accs.sequence.sequence;
|
||||||
accs.message.persist = data.persist;
|
accs.message.persist = data.persist;
|
||||||
|
accs.message.consistency_level = match data.consistency_level {
|
||||||
|
ConsistencyLevel::Confirmed => 1,
|
||||||
|
ConsistencyLevel::Finalized => 32,
|
||||||
|
};
|
||||||
|
|
||||||
// Create message account
|
// Create message account
|
||||||
accs.message
|
accs.message
|
||||||
|
|
|
@ -111,6 +111,7 @@ pub struct PostVAAData {
|
||||||
pub emitter_chain: u16,
|
pub emitter_chain: u16,
|
||||||
pub emitter_address: ForeignAddress,
|
pub emitter_address: ForeignAddress,
|
||||||
pub sequence: u64,
|
pub sequence: u64,
|
||||||
|
pub consistency_level: u8,
|
||||||
pub payload: Vec<u8>,
|
pub payload: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,6 +168,7 @@ pub fn post_vaa(ctx: &ExecutionContext, accs: &mut PostVAA, vaa: PostVAAData) ->
|
||||||
accs.message.emitter_address = vaa.emitter_address;
|
accs.message.emitter_address = vaa.emitter_address;
|
||||||
accs.message.sequence = vaa.sequence;
|
accs.message.sequence = vaa.sequence;
|
||||||
accs.message.payload = vaa.payload;
|
accs.message.payload = vaa.payload;
|
||||||
|
accs.message.consistency_level = vaa.consistency_level;
|
||||||
accs.message
|
accs.message
|
||||||
.create(&msg_derivation, ctx, accs.payer.key, Exempt)?;
|
.create(&msg_derivation, ctx, accs.payer.key, Exempt)?;
|
||||||
}
|
}
|
||||||
|
@ -218,6 +220,7 @@ fn check_integrity<'r>(
|
||||||
v.write_u16::<BigEndian>(vaa.emitter_chain)?;
|
v.write_u16::<BigEndian>(vaa.emitter_chain)?;
|
||||||
v.write(&vaa.emitter_address)?;
|
v.write(&vaa.emitter_address)?;
|
||||||
v.write_u64::<BigEndian>(vaa.sequence)?;
|
v.write_u64::<BigEndian>(vaa.sequence)?;
|
||||||
|
v.write_u8(vaa.consistency_level)?;
|
||||||
v.write(&vaa.payload)?;
|
v.write(&vaa.payload)?;
|
||||||
v.into_inner()
|
v.into_inner()
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,6 +28,7 @@ use crate::{
|
||||||
SignatureSet,
|
SignatureSet,
|
||||||
SignatureSetDerivationData,
|
SignatureSetDerivationData,
|
||||||
},
|
},
|
||||||
|
types::ConsistencyLevel,
|
||||||
InitializeData,
|
InitializeData,
|
||||||
PayloadMessage,
|
PayloadMessage,
|
||||||
PostMessageData,
|
PostMessageData,
|
||||||
|
@ -83,6 +84,7 @@ pub fn post_message(
|
||||||
nonce: u32,
|
nonce: u32,
|
||||||
payload: Vec<u8>,
|
payload: Vec<u8>,
|
||||||
persist: bool,
|
persist: bool,
|
||||||
|
commitment: ConsistencyLevel,
|
||||||
) -> solitaire::Result<(Pubkey, Instruction)> {
|
) -> solitaire::Result<(Pubkey, Instruction)> {
|
||||||
let bridge = Bridge::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
|
let bridge = Bridge::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
|
||||||
let fee_collector = FeeCollector::<'_>::key(None, &program_id);
|
let fee_collector = FeeCollector::<'_>::key(None, &program_id);
|
||||||
|
@ -124,6 +126,7 @@ pub fn post_message(
|
||||||
nonce,
|
nonce,
|
||||||
payload: payload.clone(),
|
payload: payload.clone(),
|
||||||
persist,
|
persist,
|
||||||
|
consistency_level: commitment,
|
||||||
})
|
})
|
||||||
.try_to_vec()?,
|
.try_to_vec()?,
|
||||||
},
|
},
|
||||||
|
@ -389,6 +392,7 @@ pub fn serialize_vaa(vaa: &PostVAAData) -> Vec<u8> {
|
||||||
v.write_u16::<BigEndian>(vaa.emitter_chain).unwrap();
|
v.write_u16::<BigEndian>(vaa.emitter_chain).unwrap();
|
||||||
v.write(&vaa.emitter_address).unwrap();
|
v.write(&vaa.emitter_address).unwrap();
|
||||||
v.write_u64::<BigEndian>(vaa.sequence).unwrap();
|
v.write_u64::<BigEndian>(vaa.sequence).unwrap();
|
||||||
|
v.write_u8(vaa.consistency_level).unwrap();
|
||||||
v.write(&vaa.payload).unwrap();
|
v.write(&vaa.payload).unwrap();
|
||||||
v.into_inner()
|
v.into_inner()
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,6 +167,9 @@ pub struct PostedMessageData {
|
||||||
/// Whether the VAA for this message should be persisted
|
/// Whether the VAA for this message should be persisted
|
||||||
pub persist: bool,
|
pub persist: bool,
|
||||||
|
|
||||||
|
/// Level of consistency requested by the emitter
|
||||||
|
pub consistency_level: u8,
|
||||||
|
|
||||||
/// Time the vaa was submitted
|
/// Time the vaa was submitted
|
||||||
pub vaa_time: u32,
|
pub vaa_time: u32,
|
||||||
|
|
||||||
|
@ -387,3 +390,10 @@ impl DeserializeGovernancePayload for GovernancePayloadTransferFees {
|
||||||
const MODULE: &'static str = "CORE";
|
const MODULE: &'static str = "CORE";
|
||||||
const ACTION: u8 = 4;
|
const ACTION: u8 = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(BorshSerialize, BorshDeserialize, Clone)]
|
||||||
|
pub enum ConsistencyLevel {
|
||||||
|
Confirmed,
|
||||||
|
Finalized,
|
||||||
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
#![allow(warnings)]
|
#![allow(warnings)]
|
||||||
|
|
||||||
use rand::Rng;
|
|
||||||
use borsh::BorshSerialize;
|
use borsh::BorshSerialize;
|
||||||
use byteorder::{
|
use byteorder::{
|
||||||
BigEndian,
|
BigEndian,
|
||||||
WriteBytesExt,
|
WriteBytesExt,
|
||||||
};
|
};
|
||||||
use hex_literal::hex;
|
use hex_literal::hex;
|
||||||
|
use rand::Rng;
|
||||||
use secp256k1::{
|
use secp256k1::{
|
||||||
Message as Secp256k1Message,
|
Message as Secp256k1Message,
|
||||||
PublicKey,
|
PublicKey,
|
||||||
|
@ -38,6 +38,10 @@ use solana_sdk::{
|
||||||
},
|
},
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
};
|
};
|
||||||
|
use solitaire::{
|
||||||
|
processors::seeded::Seeded,
|
||||||
|
AccountState,
|
||||||
|
};
|
||||||
use std::{
|
use std::{
|
||||||
convert::TryInto,
|
convert::TryInto,
|
||||||
io::{
|
io::{
|
||||||
|
@ -49,10 +53,6 @@ use std::{
|
||||||
SystemTime,
|
SystemTime,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use solitaire::{
|
|
||||||
processors::seeded::Seeded,
|
|
||||||
AccountState,
|
|
||||||
};
|
|
||||||
|
|
||||||
use bridge::{
|
use bridge::{
|
||||||
accounts::{
|
accounts::{
|
||||||
|
@ -125,7 +125,7 @@ fn run_integration_tests() {
|
||||||
let mut context = Context {
|
let mut context = Context {
|
||||||
public: public_keys,
|
public: public_keys,
|
||||||
secret: secret_keys,
|
secret: secret_keys,
|
||||||
seq: Sequencer {
|
seq: Sequencer {
|
||||||
sequences: std::collections::HashMap::new(),
|
sequences: std::collections::HashMap::new(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -158,7 +158,8 @@ fn test_initialize(context: &mut Context) {
|
||||||
let now = std::time::SystemTime::now()
|
let now = std::time::SystemTime::now()
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_secs() - 10;
|
.as_secs()
|
||||||
|
- 10;
|
||||||
|
|
||||||
common::initialize(client, program, payer, &*context.public.clone(), 500, 5000);
|
common::initialize(client, program, payer, &*context.public.clone(), 500, 5000);
|
||||||
common::sync(client, payer);
|
common::sync(client, payer);
|
||||||
|
@ -213,7 +214,8 @@ fn test_bridge_messages(context: &mut Context) {
|
||||||
|
|
||||||
// Emulate Guardian behaviour, verifying the data and publishing signatures/VAA.
|
// Emulate Guardian behaviour, verifying the data and publishing signatures/VAA.
|
||||||
let (vaa, body, body_hash) = common::generate_vaa(&emitter, message.clone(), nonce, 0, 1);
|
let (vaa, body, body_hash) = common::generate_vaa(&emitter, message.clone(), nonce, 0, 1);
|
||||||
common::verify_signatures(client, program, payer, body, body_hash, &context.secret, 0).unwrap();
|
common::verify_signatures(client, program, payer, body, body_hash, &context.secret, 0)
|
||||||
|
.unwrap();
|
||||||
common::post_vaa(client, program, payer, vaa).unwrap();
|
common::post_vaa(client, program, payer, vaa).unwrap();
|
||||||
common::sync(client, payer);
|
common::sync(client, payer);
|
||||||
|
|
||||||
|
@ -246,7 +248,8 @@ fn test_bridge_messages(context: &mut Context) {
|
||||||
|
|
||||||
for (signature, secret_key) in signatures.signatures.iter().zip(context.secret.iter()) {
|
for (signature, secret_key) in signatures.signatures.iter().zip(context.secret.iter()) {
|
||||||
// Sign message locally.
|
// Sign message locally.
|
||||||
let (local_sig, recover_id) = secp256k1::sign(&Secp256k1Message::parse(&body_hash), &secret_key);
|
let (local_sig, recover_id) =
|
||||||
|
secp256k1::sign(&Secp256k1Message::parse(&body_hash), &secret_key);
|
||||||
|
|
||||||
// Combine recoverify with signature to match 65 byte layout.
|
// Combine recoverify with signature to match 65 byte layout.
|
||||||
let mut signature_bytes = [0u8; 65];
|
let mut signature_bytes = [0u8; 65];
|
||||||
|
@ -311,7 +314,8 @@ fn test_bridge_messages(context: &mut Context) {
|
||||||
|
|
||||||
for (signature, secret_key) in signatures.signatures.iter().zip(context.secret.iter()) {
|
for (signature, secret_key) in signatures.signatures.iter().zip(context.secret.iter()) {
|
||||||
// Sign message locally.
|
// Sign message locally.
|
||||||
let (local_sig, recover_id) = secp256k1::sign(&Secp256k1Message::parse(&body_hash), &secret_key);
|
let (local_sig, recover_id) =
|
||||||
|
secp256k1::sign(&Secp256k1Message::parse(&body_hash), &secret_key);
|
||||||
|
|
||||||
// Combine recoverify with signature to match 65 byte layout.
|
// Combine recoverify with signature to match 65 byte layout.
|
||||||
let mut signature_bytes = [0u8; 65];
|
let mut signature_bytes = [0u8; 65];
|
||||||
|
@ -342,7 +346,8 @@ fn test_persistent_bridge_messages(context: &mut Context) {
|
||||||
message.clone(),
|
message.clone(),
|
||||||
4999,
|
4999,
|
||||||
true,
|
true,
|
||||||
).is_err());
|
)
|
||||||
|
.is_err());
|
||||||
|
|
||||||
// Check current balance to verify the right fee is going to be taken.
|
// Check current balance to verify the right fee is going to be taken.
|
||||||
let fee_collector = FeeCollector::key(None, &program);
|
let fee_collector = FeeCollector::key(None, &program);
|
||||||
|
@ -400,7 +405,8 @@ fn test_persistent_bridge_messages(context: &mut Context) {
|
||||||
|
|
||||||
for (signature, secret_key) in signatures.signatures.iter().zip(context.secret.iter()) {
|
for (signature, secret_key) in signatures.signatures.iter().zip(context.secret.iter()) {
|
||||||
// Sign message locally.
|
// Sign message locally.
|
||||||
let (local_sig, recover_id) = secp256k1::sign(&Secp256k1Message::parse(&body_hash), &secret_key);
|
let (local_sig, recover_id) =
|
||||||
|
secp256k1::sign(&Secp256k1Message::parse(&body_hash), &secret_key);
|
||||||
|
|
||||||
// Combine recoverify with signature to match 65 byte layout.
|
// Combine recoverify with signature to match 65 byte layout.
|
||||||
let mut signature_bytes = [0u8; 65];
|
let mut signature_bytes = [0u8; 65];
|
||||||
|
@ -454,7 +460,8 @@ fn test_invalid_emitter(context: &mut Context) {
|
||||||
instruction,
|
instruction,
|
||||||
],
|
],
|
||||||
solana_sdk::commitment_config::CommitmentConfig::processed(),
|
solana_sdk::commitment_config::CommitmentConfig::processed(),
|
||||||
).is_err());
|
)
|
||||||
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_duplicate_messages_fail(context: &mut Context) {
|
fn test_duplicate_messages_fail(context: &mut Context) {
|
||||||
|
@ -489,7 +496,8 @@ fn test_duplicate_messages_fail(context: &mut Context) {
|
||||||
message.clone(),
|
message.clone(),
|
||||||
10_000,
|
10_000,
|
||||||
false,
|
false,
|
||||||
).is_err());
|
)
|
||||||
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_guardian_set_change(context: &mut Context) {
|
fn test_guardian_set_change(context: &mut Context) {
|
||||||
|
@ -500,7 +508,8 @@ fn test_guardian_set_change(context: &mut Context) {
|
||||||
let now = std::time::SystemTime::now()
|
let now = std::time::SystemTime::now()
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_secs() - 10;
|
.as_secs()
|
||||||
|
- 10;
|
||||||
|
|
||||||
// Upgrade the guardian set with a new set of guardians.
|
// Upgrade the guardian set with a new set of guardians.
|
||||||
let (new_public_keys, new_secret_keys) = common::generate_keys(1);
|
let (new_public_keys, new_secret_keys) = common::generate_keys(1);
|
||||||
|
@ -618,7 +627,8 @@ fn test_guardian_set_change(context: &mut Context) {
|
||||||
|
|
||||||
for (signature, secret_key) in signatures.signatures.iter().zip(context.secret.iter()) {
|
for (signature, secret_key) in signatures.signatures.iter().zip(context.secret.iter()) {
|
||||||
// Sign message locally.
|
// Sign message locally.
|
||||||
let (local_sig, recover_id) = secp256k1::sign(&Secp256k1Message::parse(&body_hash), &secret_key);
|
let (local_sig, recover_id) =
|
||||||
|
secp256k1::sign(&Secp256k1Message::parse(&body_hash), &secret_key);
|
||||||
|
|
||||||
// Combine recoverify with signature to match 65 byte layout.
|
// Combine recoverify with signature to match 65 byte layout.
|
||||||
let mut signature_bytes = [0u8; 65];
|
let mut signature_bytes = [0u8; 65];
|
||||||
|
@ -801,7 +811,8 @@ fn test_set_fees(context: &mut Context) {
|
||||||
|
|
||||||
for (signature, secret_key) in signatures.signatures.iter().zip(context.secret.iter()) {
|
for (signature, secret_key) in signatures.signatures.iter().zip(context.secret.iter()) {
|
||||||
// Sign message locally.
|
// Sign message locally.
|
||||||
let (local_sig, recover_id) = secp256k1::sign(&Secp256k1Message::parse(&body_hash), &secret_key);
|
let (local_sig, recover_id) =
|
||||||
|
secp256k1::sign(&Secp256k1Message::parse(&body_hash), &secret_key);
|
||||||
|
|
||||||
// Combine recoverify with signature to match 65 byte layout.
|
// Combine recoverify with signature to match 65 byte layout.
|
||||||
let mut signature_bytes = [0u8; 65];
|
let mut signature_bytes = [0u8; 65];
|
||||||
|
@ -851,7 +862,8 @@ fn test_set_fees_fails(context: &mut Context) {
|
||||||
message_key,
|
message_key,
|
||||||
emitter.pubkey(),
|
emitter.pubkey(),
|
||||||
sequence,
|
sequence,
|
||||||
).is_err());
|
)
|
||||||
|
.is_err());
|
||||||
common::sync(client, payer);
|
common::sync(client, payer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -961,7 +973,8 @@ fn test_free_fees(context: &mut Context) {
|
||||||
|
|
||||||
for (signature, secret_key) in signatures.signatures.iter().zip(context.secret.iter()) {
|
for (signature, secret_key) in signatures.signatures.iter().zip(context.secret.iter()) {
|
||||||
// Sign message locally.
|
// Sign message locally.
|
||||||
let (local_sig, recover_id) = secp256k1::sign(&Secp256k1Message::parse(&body_hash), &secret_key);
|
let (local_sig, recover_id) =
|
||||||
|
secp256k1::sign(&Secp256k1Message::parse(&body_hash), &secret_key);
|
||||||
|
|
||||||
// Combine recoverify with signature to match 65 byte layout.
|
// Combine recoverify with signature to match 65 byte layout.
|
||||||
let mut signature_bytes = [0u8; 65];
|
let mut signature_bytes = [0u8; 65];
|
||||||
|
@ -1113,7 +1126,8 @@ fn test_transfer_too_much(context: &mut Context) {
|
||||||
emitter.pubkey(),
|
emitter.pubkey(),
|
||||||
payer.pubkey(),
|
payer.pubkey(),
|
||||||
sequence,
|
sequence,
|
||||||
).is_err());
|
)
|
||||||
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_foreign_bridge_messages(context: &mut Context) {
|
fn test_foreign_bridge_messages(context: &mut Context) {
|
||||||
|
@ -1170,7 +1184,8 @@ fn test_foreign_bridge_messages(context: &mut Context) {
|
||||||
|
|
||||||
for (signature, secret_key) in signatures.signatures.iter().zip(context.secret.iter()) {
|
for (signature, secret_key) in signatures.signatures.iter().zip(context.secret.iter()) {
|
||||||
// Sign message locally.
|
// Sign message locally.
|
||||||
let (local_sig, recover_id) = secp256k1::sign(&Secp256k1Message::parse(&body_hash), &secret_key);
|
let (local_sig, recover_id) =
|
||||||
|
secp256k1::sign(&Secp256k1Message::parse(&body_hash), &secret_key);
|
||||||
|
|
||||||
// Combine recoverify with signature to match 65 byte layout.
|
// Combine recoverify with signature to match 65 byte layout.
|
||||||
let mut signature_bytes = [0u8; 65];
|
let mut signature_bytes = [0u8; 65];
|
||||||
|
|
|
@ -63,8 +63,7 @@ impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>, const Seed: &'static str> Peel<'a, 'b,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Peel a Mutable key.
|
/// Peel a Mutable key.
|
||||||
impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for Mut<T>
|
impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for Mut<T> {
|
||||||
{
|
|
||||||
fn peel<I>(mut ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
|
fn peel<I>(mut ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
|
||||||
ctx.immutable = false;
|
ctx.immutable = false;
|
||||||
match ctx.info().is_writable {
|
match ctx.info().is_writable {
|
||||||
|
|
|
@ -49,6 +49,7 @@ pub struct ParsedVAA {
|
||||||
pub emitter_chain: u16,
|
pub emitter_chain: u16,
|
||||||
pub emitter_address: Vec<u8>,
|
pub emitter_address: Vec<u8>,
|
||||||
pub sequence: u64,
|
pub sequence: u64,
|
||||||
|
pub consistency_level: u8,
|
||||||
pub payload: Vec<u8>,
|
pub payload: Vec<u8>,
|
||||||
|
|
||||||
pub hash: Vec<u8>,
|
pub hash: Vec<u8>,
|
||||||
|
@ -72,6 +73,7 @@ impl ParsedVAA {
|
||||||
12 uint16 emitter_chain
|
12 uint16 emitter_chain
|
||||||
14 [32]uint8 emitter_address
|
14 [32]uint8 emitter_address
|
||||||
46 uint64 sequence
|
46 uint64 sequence
|
||||||
|
46 uint8 consistency_level
|
||||||
54 []uint8 payload
|
54 []uint8 payload
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -85,7 +87,8 @@ impl ParsedVAA {
|
||||||
pub const VAA_EMITTER_CHAIN_POS: usize = 12;
|
pub const VAA_EMITTER_CHAIN_POS: usize = 12;
|
||||||
pub const VAA_EMITTER_ADDRESS_POS: usize = 14;
|
pub const VAA_EMITTER_ADDRESS_POS: usize = 14;
|
||||||
pub const VAA_SEQUENCE_POS: usize = 46;
|
pub const VAA_SEQUENCE_POS: usize = 46;
|
||||||
pub const VAA_PAYLOAD_POS: usize = 54;
|
pub const VAA_CONSISTENCY_LEVEL_POS: usize = 54;
|
||||||
|
pub const VAA_PAYLOAD_POS: usize = 55;
|
||||||
|
|
||||||
// Signature data offsets in the signature block
|
// Signature data offsets in the signature block
|
||||||
pub const SIG_DATA_POS: usize = 1;
|
pub const SIG_DATA_POS: usize = 1;
|
||||||
|
@ -123,6 +126,7 @@ impl ParsedVAA {
|
||||||
.get_bytes32(body_offset + Self::VAA_EMITTER_ADDRESS_POS)
|
.get_bytes32(body_offset + Self::VAA_EMITTER_ADDRESS_POS)
|
||||||
.to_vec();
|
.to_vec();
|
||||||
let sequence = data.get_u64(body_offset + Self::VAA_SEQUENCE_POS);
|
let sequence = data.get_u64(body_offset + Self::VAA_SEQUENCE_POS);
|
||||||
|
let consistency_level = data.get_u8(body_offset + Self::VAA_CONSISTENCY_LEVEL_POS);
|
||||||
let payload = data[body_offset + Self::VAA_PAYLOAD_POS..].to_vec();
|
let payload = data[body_offset + Self::VAA_PAYLOAD_POS..].to_vec();
|
||||||
|
|
||||||
Ok(ParsedVAA {
|
Ok(ParsedVAA {
|
||||||
|
@ -134,6 +138,7 @@ impl ParsedVAA {
|
||||||
emitter_chain,
|
emitter_chain,
|
||||||
emitter_address,
|
emitter_address,
|
||||||
sequence,
|
sequence,
|
||||||
|
consistency_level,
|
||||||
payload,
|
payload,
|
||||||
hash,
|
hash,
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue