package ibctesting import ( "testing" "time" "github.com/stretchr/testify/require" "git.cw.tr/mukan-network/mukan-sdk/codec" codectypes "git.cw.tr/mukan-network/mukan-sdk/codec/types" kmultisig "git.cw.tr/mukan-network/mukan-sdk/crypto/keys/multisig" "git.cw.tr/mukan-network/mukan-sdk/crypto/keys/secp256k1" cryptotypes "git.cw.tr/mukan-network/mukan-sdk/crypto/types" "git.cw.tr/mukan-network/mukan-sdk/crypto/types/multisig" "git.cw.tr/mukan-network/mukan-sdk/types/tx/signing" transfertypes "git.cw.tr/mukan-network/mukan-ibc/modules/apps/transfer/types" clienttypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/02-client/types" connectiontypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/03-connection/types" channeltypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/04-channel/types" commitmenttypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/23-commitment/types" commitmenttypesv2 "git.cw.tr/mukan-network/mukan-ibc/modules/core/23-commitment/types/v2" host "git.cw.tr/mukan-network/mukan-ibc/modules/core/24-host" "git.cw.tr/mukan-network/mukan-ibc/modules/core/exported" solomachine "git.cw.tr/mukan-network/mukan-ibc/modules/light-clients/06-solomachine" ) var ( clientIDSolomachine = "client-on-solomachine" // clientID generated on solo machine side connectionIDSolomachine = "connection-on-solomachine" // connectionID generated on solo machine side channelIDSolomachine = "channel-on-solomachine" // channelID generated on solo machine side ) // DefaultSolomachineClientID is the default solo machine client id used for testing var DefaultSolomachineClientID = "06-solomachine-0" // Solomachine is a testing helper used to simulate a counterparty // solo machine client. type Solomachine struct { t *testing.T cdc codec.BinaryCodec ClientID string PrivateKeys []cryptotypes.PrivKey // keys used for signing PublicKeys []cryptotypes.PubKey // keys used for generating solo machine pub key PublicKey cryptotypes.PubKey // key used for verification Sequence uint64 Time uint64 Diversifier string } // NewSolomachine returns a new solomachine instance with an `nKeys` amount of // generated private/public key pairs and a sequence starting at 1. If nKeys // is greater than 1 then a multisig public key is used. func NewSolomachine(t *testing.T, cdc codec.BinaryCodec, clientID, diversifier string, nKeys uint64) *Solomachine { t.Helper() privKeys, pubKeys, pk := GenerateKeys(t, nKeys) return &Solomachine{ t: t, cdc: cdc, ClientID: clientID, PrivateKeys: privKeys, PublicKeys: pubKeys, PublicKey: pk, Sequence: 1, Time: 10, Diversifier: diversifier, } } // GenerateKeys generates a new set of secp256k1 private keys and public keys. // If the number of keys is greater than one then the public key returned represents // a multisig public key. The private keys are used for signing, the public // keys are used for generating the public key and the public key is used for // solo machine verification. The usage of secp256k1 is entirely arbitrary. // The key type can be swapped for any key type supported by the PublicKey // interface, if needed. The same is true for the amino based Multisignature // public key. func GenerateKeys(t *testing.T, n uint64) ([]cryptotypes.PrivKey, []cryptotypes.PubKey, cryptotypes.PubKey) { t.Helper() require.NotEqual(t, uint64(0), n, "generation of zero keys is not allowed") privKeys := make([]cryptotypes.PrivKey, n) pubKeys := make([]cryptotypes.PubKey, n) for i := uint64(0); i < n; i++ { privKeys[i] = secp256k1.GenPrivKey() pubKeys[i] = privKeys[i].PubKey() } var pk cryptotypes.PubKey if len(privKeys) > 1 { // generate multi sig pk pk = kmultisig.NewLegacyAminoPubKey(int(n), pubKeys) } else { pk = privKeys[0].PubKey() } return privKeys, pubKeys, pk } // ClientState returns a new solo machine ClientState instance. func (solo *Solomachine) ClientState() *solomachine.ClientState { return solomachine.NewClientState(solo.Sequence, solo.ConsensusState()) } // ConsensusState returns a new solo machine ConsensusState instance func (solo *Solomachine) ConsensusState() *solomachine.ConsensusState { publicKey, err := codectypes.NewAnyWithValue(solo.PublicKey) require.NoError(solo.t, err) return &solomachine.ConsensusState{ PublicKey: publicKey, Diversifier: solo.Diversifier, Timestamp: solo.Time, } } // GetHeight returns an exported.Height with Sequence as RevisionHeight func (solo *Solomachine) GetHeight() exported.Height { return clienttypes.NewHeight(0, solo.Sequence) } // CreateClient creates an on-chain client on the provided chain. func (solo *Solomachine) CreateClient(chain *TestChain) string { msgCreateClient, err := clienttypes.NewMsgCreateClient(solo.ClientState(), solo.ConsensusState(), chain.SenderAccount.GetAddress().String()) require.NoError(solo.t, err) res, err := chain.SendMsgs(msgCreateClient) require.NoError(solo.t, err) require.NotNil(solo.t, res) clientID, err := ParseClientIDFromEvents(res.Events) require.NoError(solo.t, err) return clientID } // UpdateClient sends a MsgUpdateClient to the provided chain and updates the given clientID. func (solo *Solomachine) UpdateClient(chain *TestChain, clientID string) { smHeader := solo.CreateHeader(solo.Diversifier) msgUpdateClient, err := clienttypes.NewMsgUpdateClient(clientID, smHeader, chain.SenderAccount.GetAddress().String()) require.NoError(solo.t, err) res, err := chain.SendMsgs(msgUpdateClient) require.NoError(solo.t, err) require.NotNil(solo.t, res) } // CreateHeader generates a new private/public key pair and creates the // necessary signature to construct a valid solo machine header. // A new diversifier will be used as well func (solo *Solomachine) CreateHeader(newDiversifier string) *solomachine.Header { // generate new private keys and signature for header newPrivKeys, newPubKeys, newPubKey := GenerateKeys(solo.t, uint64(len(solo.PrivateKeys))) publicKey, err := codectypes.NewAnyWithValue(newPubKey) require.NoError(solo.t, err) data := &solomachine.HeaderData{ NewPubKey: publicKey, NewDiversifier: newDiversifier, } dataBz, err := solo.cdc.Marshal(data) require.NoError(solo.t, err) signBytes := &solomachine.SignBytes{ Sequence: solo.Sequence, Timestamp: solo.Time, Diversifier: solo.Diversifier, Path: []byte(solomachine.SentinelHeaderPath), Data: dataBz, } bz, err := solo.cdc.Marshal(signBytes) require.NoError(solo.t, err) sig := solo.GenerateSignature(bz) header := &solomachine.Header{ Timestamp: solo.Time, Signature: sig, NewPublicKey: publicKey, NewDiversifier: newDiversifier, } // assumes successful header update solo.Sequence++ solo.Time++ solo.PrivateKeys = newPrivKeys solo.PublicKeys = newPubKeys solo.PublicKey = newPubKey solo.Diversifier = newDiversifier return header } // CreateMisbehaviour constructs testing misbehaviour for the solo machine client // by signing over two different data bytes at the same sequence. func (solo *Solomachine) CreateMisbehaviour() *solomachine.Misbehaviour { merklePath := commitmenttypes.NewMerklePath(host.FullClientStateKey("counterparty")) path, err := solo.cdc.Marshal(&merklePath) require.NoError(solo.t, err) data, err := solo.cdc.Marshal(solo.ClientState()) require.NoError(solo.t, err) signBytes := &solomachine.SignBytes{ Sequence: solo.Sequence, Timestamp: solo.Time, Diversifier: solo.Diversifier, Path: path, Data: data, } bz, err := solo.cdc.Marshal(signBytes) require.NoError(solo.t, err) sig := solo.GenerateSignature(bz) signatureOne := solomachine.SignatureAndData{ Signature: sig, Path: path, Data: data, Timestamp: solo.Time, } // misbehaviour signaturess can have different timestamps solo.Time++ merklePath = commitmenttypes.NewMerklePath(host.FullConsensusStateKey("counterparty", clienttypes.NewHeight(0, 1))) path, err = solo.cdc.Marshal(&merklePath) require.NoError(solo.t, err) data, err = solo.cdc.Marshal(solo.ConsensusState()) require.NoError(solo.t, err) signBytes = &solomachine.SignBytes{ Sequence: solo.Sequence, Timestamp: solo.Time, Diversifier: solo.Diversifier, Path: path, Data: data, } bz, err = solo.cdc.Marshal(signBytes) require.NoError(solo.t, err) sig = solo.GenerateSignature(bz) signatureTwo := solomachine.SignatureAndData{ Signature: sig, Path: path, Data: data, Timestamp: solo.Time, } return &solomachine.Misbehaviour{ Sequence: solo.Sequence, SignatureOne: &signatureOne, SignatureTwo: &signatureTwo, } } // ConnOpenInit initializes a connection on the provided chain given a solo machine clientID. func (solo *Solomachine) ConnOpenInit(chain *TestChain, clientID string) string { msgConnOpenInit := connectiontypes.NewMsgConnectionOpenInit( clientID, clientIDSolomachine, // clientID generated on solo machine side chain.GetPrefix(), DefaultOpenInitVersion, DefaultDelayPeriod, chain.SenderAccount.GetAddress().String(), ) res, err := chain.SendMsgs(msgConnOpenInit) require.NoError(solo.t, err) require.NotNil(solo.t, res) connectionID, err := ParseConnectionIDFromEvents(res.Events) require.NoError(solo.t, err) return connectionID } // ConnOpenAck performs the connection open ack handshake step on the tendermint chain for the associated // solo machine client. func (solo *Solomachine) ConnOpenAck(chain *TestChain, clientID, connectionID string) { tryProof := solo.GenerateConnOpenTryProof(clientID, connectionID) msgConnOpenAck := connectiontypes.NewMsgConnectionOpenAck( connectionID, connectionIDSolomachine, tryProof, clienttypes.ZeroHeight(), ConnectionVersion, chain.SenderAccount.GetAddress().String(), ) res, err := chain.SendMsgs(msgConnOpenAck) require.NoError(solo.t, err) require.NotNil(solo.t, res) } // ChanOpenInit initializes a channel on the provided chain given a solo machine connectionID. func (solo *Solomachine) ChanOpenInit(chain *TestChain, connectionID string) string { msgChanOpenInit := channeltypes.NewMsgChannelOpenInit( transfertypes.PortID, transfertypes.V1, channeltypes.UNORDERED, []string{connectionID}, transfertypes.PortID, chain.SenderAccount.GetAddress().String(), ) res, err := chain.SendMsgs(msgChanOpenInit) require.NoError(solo.t, err) require.NotNil(solo.t, res) channelID, err := ParseChannelIDFromEvents(res.Events) require.NoError(solo.t, err) return channelID } // ChanOpenAck performs the channel open ack handshake step on the tendermint chain for the associated // solo machine client. func (solo *Solomachine) ChanOpenAck(chain *TestChain, channelID string) { tryProof := solo.GenerateChanOpenTryProof(transfertypes.PortID, transfertypes.V1, channelID) msgChanOpenAck := channeltypes.NewMsgChannelOpenAck( transfertypes.PortID, channelID, channelIDSolomachine, transfertypes.V1, tryProof, clienttypes.ZeroHeight(), chain.SenderAccount.GetAddress().String(), ) res, err := chain.SendMsgs(msgChanOpenAck) require.NoError(solo.t, err) require.NotNil(solo.t, res) } // ChanCloseConfirm performs the channel close confirm handshake step on the tendermint chain for the associated // solo machine client. func (solo *Solomachine) ChanCloseConfirm(chain *TestChain, portID, channelID string) { initProof := solo.GenerateChanClosedProof(portID, transfertypes.V1, channelID) msgChanCloseConfirm := channeltypes.NewMsgChannelCloseConfirm( portID, channelID, initProof, clienttypes.ZeroHeight(), chain.SenderAccount.GetAddress().String(), ) res, err := chain.SendMsgs(msgChanCloseConfirm) require.NoError(solo.t, err) require.NotNil(solo.t, res) } // SendTransfer constructs a MsgTransfer and sends the message to the given chain. Any number of optional // functions can be provided which will modify the MsgTransfer before SendMsgs is called. func (solo *Solomachine) SendTransfer(chain *TestChain, portID, channelID string, fns ...func(*transfertypes.MsgTransfer)) channeltypes.Packet { msgTransfer := transfertypes.NewMsgTransfer( portID, channelID, TestCoin, chain.SenderAccount.GetAddress().String(), chain.SenderAccount.GetAddress().String(), clienttypes.ZeroHeight(), uint64(chain.GetContext().BlockTime().Add(time.Hour).UnixNano()), "", ) for _, fn := range fns { fn(msgTransfer) } res, err := chain.SendMsgs(msgTransfer) require.NoError(solo.t, err) packet, err := ParseV1PacketFromEvents(res.Events) require.NoError(solo.t, err) return packet } // RecvPacket creates a commitment proof and broadcasts a new MsgRecvPacket. func (solo *Solomachine) RecvPacket(chain *TestChain, packet channeltypes.Packet) { proofCommitment := solo.GenerateCommitmentProof(packet) msgRecvPacket := channeltypes.NewMsgRecvPacket( packet, proofCommitment, clienttypes.ZeroHeight(), chain.SenderAccount.GetAddress().String(), ) res, err := chain.SendMsgs(msgRecvPacket) require.NoError(solo.t, err) require.NotNil(solo.t, res) } // AcknowledgePacket creates an acknowledgement proof and broadcasts a MsgAcknowledgement. func (solo *Solomachine) AcknowledgePacket(chain *TestChain, packet channeltypes.Packet) { ackProof := solo.GenerateAcknowledgementProof(packet) transferAck := channeltypes.NewResultAcknowledgement([]byte{byte(1)}).Acknowledgement() msgAcknowledgement := channeltypes.NewMsgAcknowledgement( packet, transferAck, ackProof, clienttypes.ZeroHeight(), chain.SenderAccount.GetAddress().String(), ) res, err := chain.SendMsgs(msgAcknowledgement) require.NoError(solo.t, err) require.NotNil(solo.t, res) } // TimeoutPacket creates a unreceived packet proof and broadcasts a MsgTimeout. func (solo *Solomachine) TimeoutPacket(chain *TestChain, packet channeltypes.Packet) { unreceivedProof := solo.GenerateReceiptAbsenceProof(packet) msgTimeout := channeltypes.NewMsgTimeout( packet, 1, // nextSequenceRecv is unused for UNORDERED channels unreceivedProof, clienttypes.ZeroHeight(), chain.SenderAccount.GetAddress().String(), ) res, err := chain.SendMsgs(msgTimeout) require.NoError(solo.t, err) require.NotNil(solo.t, res) } // TimeoutPacketOnClose creates a channel closed and unreceived packet proof and broadcasts a MsgTimeoutOnClose. func (solo *Solomachine) TimeoutPacketOnClose(chain *TestChain, packet channeltypes.Packet, channelID string) { closedProof := solo.GenerateChanClosedProof(transfertypes.PortID, transfertypes.V1, channelID) unreceivedProof := solo.GenerateReceiptAbsenceProof(packet) msgTimeout := channeltypes.NewMsgTimeoutOnClose( packet, 1, // nextSequenceRecv is unused for UNORDERED channels unreceivedProof, closedProof, clienttypes.ZeroHeight(), chain.SenderAccount.GetAddress().String(), ) res, err := chain.SendMsgs(msgTimeout) require.NoError(solo.t, err) require.NotNil(solo.t, res) } // GenerateSignature uses the stored private keys to generate a signature // over the sign bytes with each key. If the amount of keys is greater than // 1 then a multisig data type is returned. func (solo *Solomachine) GenerateSignature(signBytes []byte) []byte { sigs := make([]signing.SignatureData, len(solo.PrivateKeys)) for i, key := range solo.PrivateKeys { sig, err := key.Sign(signBytes) require.NoError(solo.t, err) sigs[i] = &signing.SingleSignatureData{ Signature: sig, } } var sigData signing.SignatureData if len(sigs) == 1 { // single public key sigData = sigs[0] } else { // generate multi signature data multiSigData := multisig.NewMultisig(len(sigs)) for i, sig := range sigs { multisig.AddSignature(multiSigData, sig, i) } sigData = multiSigData } protoSigData := signing.SignatureDataToProto(sigData) bz, err := solo.cdc.Marshal(protoSigData) require.NoError(solo.t, err) return bz } // GenerateProof takes in solo machine sign bytes, generates a signature and marshals it as a proof. // The solo machine sequence is incremented. func (solo *Solomachine) GenerateProof(signBytes *solomachine.SignBytes) []byte { bz, err := solo.cdc.Marshal(signBytes) require.NoError(solo.t, err) sig := solo.GenerateSignature(bz) signatureDoc := &solomachine.TimestampedSignatureData{ SignatureData: sig, Timestamp: solo.Time, } proof, err := solo.cdc.Marshal(signatureDoc) require.NoError(solo.t, err) solo.Sequence++ return proof } // GenerateConnOpenTryProof generates the proofTry required for the connection open ack handshake step. // The clientID, connectionID provided represent the clientID and connectionID created on the counterparty chain, that is the tendermint chain. func (solo *Solomachine) GenerateConnOpenTryProof(counterpartyClientID, counterpartyConnectionID string) []byte { counterparty := connectiontypes.NewCounterparty(counterpartyClientID, counterpartyConnectionID, prefix) connection := connectiontypes.NewConnectionEnd(connectiontypes.TRYOPEN, clientIDSolomachine, counterparty, []*connectiontypes.Version{ConnectionVersion}, DefaultDelayPeriod) data, err := solo.cdc.Marshal(&connection) require.NoError(solo.t, err) path := host.ConnectionKey(connectionIDSolomachine) signBytes := &solomachine.SignBytes{ Sequence: solo.Sequence, Timestamp: solo.Time, Diversifier: solo.Diversifier, Path: path, Data: data, } return solo.GenerateProof(signBytes) } // GenerateChanOpenTryProof generates the proofTry required for the channel open ack handshake step. // The channelID provided represents the channelID created on the counterparty chain, that is the tendermint chain. func (solo *Solomachine) GenerateChanOpenTryProof(portID, version, counterpartyChannelID string) []byte { counterparty := channeltypes.NewCounterparty(portID, counterpartyChannelID) channel := channeltypes.NewChannel(channeltypes.TRYOPEN, channeltypes.UNORDERED, counterparty, []string{connectionIDSolomachine}, version) data, err := solo.cdc.Marshal(&channel) require.NoError(solo.t, err) path := host.ChannelKey(portID, channelIDSolomachine) signBytes := &solomachine.SignBytes{ Sequence: solo.Sequence, Timestamp: solo.Time, Diversifier: solo.Diversifier, Path: path, Data: data, } return solo.GenerateProof(signBytes) } // GenerateChanClosedProof generates a channel closed proof. // The channelID provided represents the channelID created on the counterparty chain, that is the tendermint chain. func (solo *Solomachine) GenerateChanClosedProof(portID, version, counterpartyChannelID string) []byte { counterparty := channeltypes.NewCounterparty(portID, counterpartyChannelID) channel := channeltypes.NewChannel(channeltypes.CLOSED, channeltypes.UNORDERED, counterparty, []string{connectionIDSolomachine}, version) data, err := solo.cdc.Marshal(&channel) require.NoError(solo.t, err) path := host.ChannelKey(portID, channelIDSolomachine) signBytes := &solomachine.SignBytes{ Sequence: solo.Sequence, Timestamp: solo.Time, Diversifier: solo.Diversifier, Path: path, Data: data, } return solo.GenerateProof(signBytes) } // GenerateCommitmentProof generates a commitment proof for the provided packet. func (solo *Solomachine) GenerateCommitmentProof(packet channeltypes.Packet) []byte { commitment := channeltypes.CommitPacket(packet) path := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) signBytes := &solomachine.SignBytes{ Sequence: solo.Sequence, Timestamp: solo.Time, Diversifier: solo.Diversifier, Path: path, Data: commitment, } return solo.GenerateProof(signBytes) } // GenerateAcknowledgementProof generates an acknowledgement proof. func (solo *Solomachine) GenerateAcknowledgementProof(packet channeltypes.Packet) []byte { transferAck := channeltypes.NewResultAcknowledgement([]byte{byte(1)}).Acknowledgement() path := host.PacketAcknowledgementKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) signBytes := &solomachine.SignBytes{ Sequence: solo.Sequence, Timestamp: solo.Time, Diversifier: solo.Diversifier, Path: path, Data: channeltypes.CommitAcknowledgement(transferAck), } return solo.GenerateProof(signBytes) } // GenerateReceiptAbsenceProof generates a receipt absence proof for the provided packet. func (solo *Solomachine) GenerateReceiptAbsenceProof(packet channeltypes.Packet) []byte { path := host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) signBytes := &solomachine.SignBytes{ Sequence: solo.Sequence, Timestamp: solo.Time, Diversifier: solo.Diversifier, Path: path, Data: nil, } return solo.GenerateProof(signBytes) } // GetClientStatePath returns the commitment path for the client state. func (solo *Solomachine) GetClientStatePath(counterpartyClientIdentifier string) commitmenttypesv2.MerklePath { path, err := commitmenttypes.ApplyPrefix(prefix, commitmenttypes.NewMerklePath(host.FullClientStateKey(counterpartyClientIdentifier))) require.NoError(solo.t, err) return path } // GetConsensusStatePath returns the commitment path for the consensus state. func (solo *Solomachine) GetConsensusStatePath(counterpartyClientIdentifier string, consensusHeight exported.Height) commitmenttypesv2.MerklePath { path, err := commitmenttypes.ApplyPrefix(prefix, commitmenttypes.NewMerklePath(host.FullConsensusStateKey(counterpartyClientIdentifier, consensusHeight))) require.NoError(solo.t, err) return path } // GetConnectionStatePath returns the commitment path for the connection state. func (solo *Solomachine) GetConnectionStatePath(connID string) commitmenttypesv2.MerklePath { connectionPath := commitmenttypes.NewMerklePath(host.ConnectionKey(connID)) path, err := commitmenttypes.ApplyPrefix(prefix, connectionPath) require.NoError(solo.t, err) return path } // GetChannelStatePath returns the commitment path for that channel state. func (solo *Solomachine) GetChannelStatePath(portID, channelID string) commitmenttypesv2.MerklePath { channelPath := commitmenttypes.NewMerklePath(host.ChannelKey(portID, channelID)) path, err := commitmenttypes.ApplyPrefix(prefix, channelPath) require.NoError(solo.t, err) return path } // GetPacketCommitmentPath returns the commitment path for a packet commitment. func (solo *Solomachine) GetPacketCommitmentPath(portID, channelID string, sequence uint64) commitmenttypesv2.MerklePath { commitmentPath := commitmenttypes.NewMerklePath(host.PacketCommitmentKey(portID, channelID, sequence)) path, err := commitmenttypes.ApplyPrefix(prefix, commitmentPath) require.NoError(solo.t, err) return path } // GetPacketAcknowledgementPath returns the commitment path for a packet acknowledgement. func (solo *Solomachine) GetPacketAcknowledgementPath(portID, channelID string, sequence uint64) commitmenttypesv2.MerklePath { ackPath := commitmenttypes.NewMerklePath(host.PacketAcknowledgementKey(portID, channelID, sequence)) path, err := commitmenttypes.ApplyPrefix(prefix, ackPath) require.NoError(solo.t, err) return path } // GetPacketReceiptPath returns the commitment path for a packet receipt // and an absent receipts. func (solo *Solomachine) GetPacketReceiptPath(portID, channelID string, sequence uint64) commitmenttypesv2.MerklePath { receiptPath := commitmenttypes.NewMerklePath(host.PacketReceiptKey(portID, channelID, sequence)) path, err := commitmenttypes.ApplyPrefix(prefix, receiptPath) require.NoError(solo.t, err) return path } // GetNextSequenceRecvPath returns the commitment path for the next sequence recv counter. func (solo *Solomachine) GetNextSequenceRecvPath(portID, channelID string) commitmenttypesv2.MerklePath { nextSequenceRecvPath := commitmenttypes.NewMerklePath(host.NextSequenceRecvKey(portID, channelID)) path, err := commitmenttypes.ApplyPrefix(prefix, nextSequenceRecvPath) require.NoError(solo.t, err) return path }