490 lines
14 KiB
Go
Executable File
490 lines
14 KiB
Go
Executable File
package contracts
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
appchainMgr "github.com/meshplus/bitxhub-core/appchain-mgr"
|
|
"github.com/meshplus/bitxhub-core/boltvm"
|
|
"github.com/meshplus/bitxhub-kit/crypto"
|
|
"github.com/meshplus/bitxhub-kit/crypto/asym"
|
|
"github.com/meshplus/bitxhub-kit/crypto/asym/ecdsa"
|
|
"github.com/meshplus/bitxhub-kit/types"
|
|
"github.com/meshplus/bitxhub-model/constant"
|
|
"github.com/meshplus/bitxhub-model/pb"
|
|
"github.com/meshplus/bitxid"
|
|
)
|
|
|
|
type InterchainManager struct {
|
|
boltvm.Stub
|
|
}
|
|
|
|
type BxhValidators struct {
|
|
Addresses []string `json:"addresses"`
|
|
}
|
|
|
|
func (x *InterchainManager) Register(method string) *boltvm.Response {
|
|
interchain, ok := x.getInterchain(method)
|
|
if !ok {
|
|
interchain = &pb.Interchain{
|
|
ID: method,
|
|
InterchainCounter: make(map[string]uint64),
|
|
ReceiptCounter: make(map[string]uint64),
|
|
SourceReceiptCounter: make(map[string]uint64),
|
|
}
|
|
x.setInterchain(method, interchain)
|
|
}
|
|
body, err := interchain.Marshal()
|
|
if err != nil {
|
|
return boltvm.Error(err.Error())
|
|
}
|
|
|
|
return boltvm.Success(body)
|
|
}
|
|
|
|
func (x *InterchainManager) DeleteInterchain(id string) *boltvm.Response {
|
|
x.Delete(AppchainKey(id))
|
|
return boltvm.Success(nil)
|
|
}
|
|
|
|
// GetInterchain returns information of the interchain count, Receipt count and SourceReceipt count by id
|
|
func (x *InterchainManager) getInterchain(id string) (*pb.Interchain, bool) {
|
|
interchain := &pb.Interchain{}
|
|
ok, data := x.Get(AppchainKey(id))
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
|
|
if err := interchain.Unmarshal(data); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if interchain.InterchainCounter == nil {
|
|
interchain.InterchainCounter = make(map[string]uint64)
|
|
}
|
|
|
|
if interchain.ReceiptCounter == nil {
|
|
interchain.ReceiptCounter = make(map[string]uint64)
|
|
}
|
|
|
|
if interchain.SourceReceiptCounter == nil {
|
|
interchain.SourceReceiptCounter = make(map[string]uint64)
|
|
}
|
|
|
|
return interchain, true
|
|
}
|
|
|
|
// GetInterchain returns information of the interchain count, Receipt count and SourceReceipt count by id
|
|
func (x *InterchainManager) setInterchain(id string, interchain *pb.Interchain) {
|
|
data, err := interchain.Marshal()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
x.Set(AppchainKey(id), data)
|
|
}
|
|
|
|
// Interchain returns information of the interchain count, Receipt count and SourceReceipt count
|
|
func (x *InterchainManager) Interchain(method string) *boltvm.Response {
|
|
ok, data := x.Get(AppchainKey(method))
|
|
if !ok {
|
|
return boltvm.Error(fmt.Errorf("this appchain does not exist").Error())
|
|
}
|
|
return boltvm.Success(data)
|
|
}
|
|
|
|
// GetInterchain returns information of the interchain count, Receipt count and SourceReceipt count by id
|
|
func (x *InterchainManager) GetInterchain(id string) *boltvm.Response {
|
|
ok, data := x.Get(AppchainKey(id))
|
|
if !ok {
|
|
return boltvm.Error(fmt.Errorf("this appchain does not exist").Error())
|
|
}
|
|
return boltvm.Success(data)
|
|
}
|
|
|
|
func (x *InterchainManager) HandleIBTP(ibtp *pb.IBTP) *boltvm.Response {
|
|
if len(strings.Split(ibtp.From, "-")) == 2 {
|
|
return x.handleUnionIBTP(ibtp)
|
|
}
|
|
|
|
interchain, _, err := x.checkAppchain(ibtp.From)
|
|
if err != nil {
|
|
return boltvm.Error(err.Error())
|
|
}
|
|
|
|
if err := x.checkIBTP(ibtp, interchain); err != nil {
|
|
return boltvm.Error(err.Error())
|
|
}
|
|
|
|
res := boltvm.Success(nil)
|
|
|
|
if pb.IBTP_INTERCHAIN == ibtp.Type {
|
|
res = x.beginTransaction(ibtp)
|
|
} else if pb.IBTP_RECEIPT_SUCCESS == ibtp.Type || pb.IBTP_RECEIPT_FAILURE == ibtp.Type {
|
|
res = x.reportTransaction(ibtp)
|
|
} else if pb.IBTP_ASSET_EXCHANGE_INIT == ibtp.Type ||
|
|
pb.IBTP_ASSET_EXCHANGE_REDEEM == ibtp.Type ||
|
|
pb.IBTP_ASSET_EXCHANGE_REFUND == ibtp.Type {
|
|
res = x.handleAssetExchange(ibtp)
|
|
}
|
|
|
|
if !res.Ok {
|
|
return res
|
|
}
|
|
|
|
x.ProcessIBTP(ibtp, interchain)
|
|
|
|
return res
|
|
}
|
|
|
|
func (x *InterchainManager) HandleIBTPs(data []byte) *boltvm.Response {
|
|
ibtps := &pb.IBTPs{}
|
|
if err := ibtps.Unmarshal(data); err != nil {
|
|
return boltvm.Error(err.Error())
|
|
}
|
|
|
|
// check if all ibtp has the same src address
|
|
if len(ibtps.Ibtps) == 0 {
|
|
return boltvm.Error("empty pack of ibtps")
|
|
}
|
|
srcChainMethod := ibtps.Ibtps[0].From
|
|
for _, ibtp := range ibtps.Ibtps {
|
|
if ibtp.From != srcChainMethod {
|
|
return boltvm.Error("ibtp pack should have the same src chain method")
|
|
}
|
|
}
|
|
|
|
interchain, _, err := x.checkAppchain(srcChainMethod)
|
|
if err != nil {
|
|
return boltvm.Error(err.Error())
|
|
}
|
|
for _, ibtp := range ibtps.Ibtps {
|
|
if err := x.checkIBTP(ibtp, interchain); err != nil {
|
|
return boltvm.Error(err.Error())
|
|
}
|
|
}
|
|
|
|
if res := x.beginMultiTargetsTransaction(srcChainMethod, ibtps); !res.Ok {
|
|
return res
|
|
}
|
|
|
|
for _, ibtp := range ibtps.Ibtps {
|
|
x.ProcessIBTP(ibtp, interchain)
|
|
}
|
|
|
|
return boltvm.Success(nil)
|
|
}
|
|
|
|
func (x *InterchainManager) checkIBTP(ibtp *pb.IBTP, interchain *pb.Interchain) error {
|
|
if ibtp.To == "" {
|
|
return fmt.Errorf("empty destination chain id")
|
|
}
|
|
|
|
if _, ok := x.getInterchain(ibtp.To); !ok {
|
|
x.Logger().WithField("chain_id", ibtp.To).Debug("target appchain does not exist")
|
|
}
|
|
|
|
srcChainInfo, err := x.getAppchainInfo(ibtp.From)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if pb.IBTP_INTERCHAIN == ibtp.Type ||
|
|
pb.IBTP_ASSET_EXCHANGE_INIT == ibtp.Type ||
|
|
pb.IBTP_ASSET_EXCHANGE_REDEEM == ibtp.Type ||
|
|
pb.IBTP_ASSET_EXCHANGE_REFUND == ibtp.Type {
|
|
if srcChainInfo.ChainType != appchainMgr.RelaychainType {
|
|
if err := x.checkPubKeyAndCaller(srcChainInfo.PublicKey); err != nil {
|
|
return fmt.Errorf("caller is not bind to ibtp from: %w", err)
|
|
}
|
|
}
|
|
idx := interchain.InterchainCounter[ibtp.To]
|
|
if ibtp.Index <= idx {
|
|
return fmt.Errorf(fmt.Sprintf("index already exists, required %d, but %d", idx+1, ibtp.Index))
|
|
}
|
|
if ibtp.Index > idx+1 {
|
|
return fmt.Errorf(fmt.Sprintf("wrong index, required %d, but %d", idx+1, ibtp.Index))
|
|
}
|
|
} else {
|
|
if srcChainInfo.ChainType != appchainMgr.RelaychainType {
|
|
destChainInfo, err := x.getAppchainInfo(ibtp.To)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := x.checkPubKeyAndCaller(destChainInfo.PublicKey); err != nil {
|
|
return fmt.Errorf("caller is not bind to ibtp to")
|
|
}
|
|
}
|
|
idx := interchain.ReceiptCounter[ibtp.To]
|
|
if ibtp.Index <= idx {
|
|
return fmt.Errorf(fmt.Sprintf("receipt index already exists, required %d, but %d", idx+1, ibtp.Index))
|
|
}
|
|
|
|
if ibtp.Index > idx+1 {
|
|
return fmt.Errorf(fmt.Sprintf("wrong receipt index, required %d, but %d", idx+1, ibtp.Index))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (x *InterchainManager) checkPubKeyAndCaller(pub string) error {
|
|
pubKeyBytes, err := base64.StdEncoding.DecodeString(pub)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pubKey, err := ecdsa.UnmarshalPublicKey(pubKeyBytes, crypto.Secp256k1)
|
|
if err != nil {
|
|
return fmt.Errorf("decrypt registerd public key error: %w", err)
|
|
}
|
|
addr, err := pubKey.Address()
|
|
if err != nil {
|
|
return fmt.Errorf("decrypt registerd public key error: %w", err)
|
|
}
|
|
if addr.String() != x.Caller() {
|
|
return fmt.Errorf("chain pub key derived address != caller")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (x *InterchainManager) checkAppchain(id string) (*pb.Interchain, *appchainMgr.Appchain, error) {
|
|
interchain, ok := x.getInterchain(id)
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("this appchain does not exist")
|
|
}
|
|
|
|
app := &appchainMgr.Appchain{}
|
|
res := x.CrossInvoke(constant.AppchainMgrContractAddr.String(), "GetAppchain", pb.String(id))
|
|
if !res.Ok {
|
|
return nil, nil, fmt.Errorf("get appchain info error: " + string(res.Result))
|
|
}
|
|
|
|
if err := json.Unmarshal(res.Result, app); err != nil {
|
|
return nil, nil, fmt.Errorf("unmarshal error: " + err.Error())
|
|
}
|
|
|
|
if app.Status != appchainMgr.AppchainAvailable {
|
|
return nil, nil, fmt.Errorf("the appchain status is " + string(app.Status) + ", can not handle IBTP")
|
|
}
|
|
|
|
return interchain, app, nil
|
|
}
|
|
|
|
// isRelayIBTP returns whether ibtp.from is relaychain type
|
|
func (x *InterchainManager) getAppchainInfo(chainMethod string) (*appchainMgr.Appchain, error) {
|
|
srcChain := &appchainMgr.Appchain{}
|
|
res := x.CrossInvoke(constant.AppchainMgrContractAddr.String(), "GetAppchain", pb.String(chainMethod))
|
|
if err := json.Unmarshal(res.Result, srcChain); err != nil {
|
|
return nil, fmt.Errorf("unmarshal appchain info error: %w", err)
|
|
}
|
|
return srcChain, nil
|
|
}
|
|
|
|
func (x *InterchainManager) ProcessIBTP(ibtp *pb.IBTP, interchain *pb.Interchain) {
|
|
m := make(map[string]uint64)
|
|
if pb.IBTP_INTERCHAIN == ibtp.Type ||
|
|
pb.IBTP_ASSET_EXCHANGE_INIT == ibtp.Type ||
|
|
pb.IBTP_ASSET_EXCHANGE_REDEEM == ibtp.Type ||
|
|
pb.IBTP_ASSET_EXCHANGE_REFUND == ibtp.Type {
|
|
if interchain.InterchainCounter == nil {
|
|
x.Logger().Info("interchain counter is nil, make one")
|
|
interchain.InterchainCounter = make(map[string]uint64)
|
|
}
|
|
interchain.InterchainCounter[ibtp.To]++
|
|
x.setInterchain(ibtp.From, interchain)
|
|
x.AddObject(x.indexMapKey(ibtp.ID()), x.GetTxHash())
|
|
m[ibtp.To] = x.GetTxIndex()
|
|
} else {
|
|
interchain.ReceiptCounter[ibtp.To] = ibtp.Index
|
|
x.setInterchain(ibtp.From, interchain)
|
|
m[ibtp.From] = x.GetTxIndex()
|
|
|
|
ic, _ := x.getInterchain(ibtp.To)
|
|
ic.SourceReceiptCounter[ibtp.From] = ibtp.Index
|
|
x.setInterchain(ibtp.To, ic)
|
|
x.SetObject(x.indexReceiptMapKey(ibtp.ID()), x.GetTxHash())
|
|
|
|
}
|
|
|
|
x.PostInterchainEvent(m)
|
|
}
|
|
|
|
func (x *InterchainManager) beginMultiTargetsTransaction(srcChainMethod string, ibtps *pb.IBTPs) *boltvm.Response {
|
|
args := make([]*pb.Arg, 0)
|
|
globalId := fmt.Sprintf("%s-%s", srcChainMethod, x.GetTxHash())
|
|
args = append(args, pb.String(globalId))
|
|
|
|
for _, ibtp := range ibtps.Ibtps {
|
|
if ibtp.Type != pb.IBTP_INTERCHAIN {
|
|
return boltvm.Error("ibtp type != IBTP_INTERCHAIN")
|
|
}
|
|
|
|
childTxId := fmt.Sprintf("%s-%s-%d", ibtp.From, ibtp.To, ibtp.Index)
|
|
args = append(args, pb.String(childTxId))
|
|
}
|
|
|
|
return x.CrossInvoke(constant.TransactionMgrContractAddr.String(), "BeginMultiTXs", args...)
|
|
}
|
|
|
|
func (x *InterchainManager) beginTransaction(ibtp *pb.IBTP) *boltvm.Response {
|
|
txId := fmt.Sprintf("%s-%s-%d", ibtp.From, ibtp.To, ibtp.Index)
|
|
return x.CrossInvoke(constant.TransactionMgrContractAddr.String(), "Begin", pb.String(txId))
|
|
}
|
|
|
|
func (x *InterchainManager) reportTransaction(ibtp *pb.IBTP) *boltvm.Response {
|
|
txId := fmt.Sprintf("%s-%s-%d", ibtp.From, ibtp.To, ibtp.Index)
|
|
result := int32(0)
|
|
if ibtp.Type == pb.IBTP_RECEIPT_FAILURE {
|
|
result = 1
|
|
}
|
|
return x.CrossInvoke(constant.TransactionMgrContractAddr.String(), "Report", pb.String(txId), pb.Int32(result))
|
|
}
|
|
|
|
func (x *InterchainManager) handleAssetExchange(ibtp *pb.IBTP) *boltvm.Response {
|
|
var method string
|
|
|
|
switch ibtp.Type {
|
|
case pb.IBTP_ASSET_EXCHANGE_INIT:
|
|
method = "Init"
|
|
case pb.IBTP_ASSET_EXCHANGE_REDEEM:
|
|
method = "Redeem"
|
|
case pb.IBTP_ASSET_EXCHANGE_REFUND:
|
|
method = "Refund"
|
|
default:
|
|
return boltvm.Error("unsupported asset exchange type")
|
|
}
|
|
|
|
return x.CrossInvoke(constant.AssetExchangeContractAddr.String(), method, pb.String(ibtp.From),
|
|
pb.String(ibtp.To), pb.Bytes(ibtp.Extra))
|
|
}
|
|
|
|
func (x *InterchainManager) GetIBTPByID(id string) *boltvm.Response {
|
|
arr := strings.Split(id, "-")
|
|
if len(arr) != 3 {
|
|
return boltvm.Error("wrong ibtp id")
|
|
}
|
|
srcAppchainMethod := arr[0]
|
|
dstAppchainMethod := arr[1]
|
|
if !bitxid.DID(srcAppchainMethod).IsValidFormat() || !bitxid.DID(dstAppchainMethod).IsValidFormat() {
|
|
return boltvm.Error("invalid format of appchain method")
|
|
}
|
|
|
|
var hash types.Hash
|
|
exist := x.GetObject(x.indexMapKey(id), &hash)
|
|
if !exist {
|
|
return boltvm.Error("this ibtp id is not existed")
|
|
}
|
|
|
|
return boltvm.Success(hash.Bytes())
|
|
}
|
|
|
|
func (x *InterchainManager) handleUnionIBTP(ibtp *pb.IBTP) *boltvm.Response {
|
|
srcRelayChainID := strings.Split(ibtp.From, "-")[0]
|
|
|
|
_, app, err := x.checkAppchain(srcRelayChainID)
|
|
if err != nil {
|
|
return boltvm.Error(err.Error())
|
|
}
|
|
|
|
if ibtp.To == "" {
|
|
return boltvm.Error("empty destination chain id")
|
|
}
|
|
if ok := x.Has(AppchainKey(ibtp.To)); !ok {
|
|
return boltvm.Error(fmt.Sprintf("target appchain does not exist: %s", ibtp.To))
|
|
}
|
|
|
|
interchain, ok := x.getInterchain(ibtp.From)
|
|
if !ok {
|
|
interchain = &pb.Interchain{
|
|
ID: ibtp.From,
|
|
}
|
|
x.setInterchain(ibtp.From, interchain)
|
|
}
|
|
|
|
if err := x.checkUnionIBTP(app, ibtp, interchain); err != nil {
|
|
return boltvm.Error(err.Error())
|
|
}
|
|
|
|
x.ProcessIBTP(ibtp, interchain)
|
|
return boltvm.Success(nil)
|
|
}
|
|
|
|
func (x *InterchainManager) checkUnionIBTP(app *appchainMgr.Appchain, ibtp *pb.IBTP, interchain *pb.Interchain) error {
|
|
if pb.IBTP_INTERCHAIN == ibtp.Type ||
|
|
pb.IBTP_ASSET_EXCHANGE_INIT == ibtp.Type ||
|
|
pb.IBTP_ASSET_EXCHANGE_REDEEM == ibtp.Type ||
|
|
pb.IBTP_ASSET_EXCHANGE_REFUND == ibtp.Type {
|
|
|
|
idx := interchain.InterchainCounter[ibtp.To]
|
|
if idx+1 != ibtp.Index {
|
|
return fmt.Errorf(fmt.Sprintf("wrong index, required %d, but %d", idx+1, ibtp.Index))
|
|
}
|
|
} else {
|
|
idx := interchain.ReceiptCounter[ibtp.To]
|
|
if idx+1 != ibtp.Index {
|
|
if interchain.SourceReceiptCounter[ibtp.To]+1 != ibtp.Index {
|
|
return fmt.Errorf("wrong receipt index, required %d, but %d", idx+1, ibtp.Index)
|
|
}
|
|
}
|
|
}
|
|
|
|
_, err := verifyMultiSign(app, ibtp, ibtp.Proof)
|
|
return err
|
|
}
|
|
|
|
// verifyMultiSign .
|
|
func verifyMultiSign(app *appchainMgr.Appchain, ibtp *pb.IBTP, proof []byte) (bool, error) {
|
|
if "" == app.Validators {
|
|
return false, fmt.Errorf("empty validators in relay chain:%s", app.ID)
|
|
}
|
|
var validators BxhValidators
|
|
if err := json.Unmarshal([]byte(app.Validators), &validators); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
m := make(map[string]struct{}, 0)
|
|
for _, validator := range validators.Addresses {
|
|
m[validator] = struct{}{}
|
|
}
|
|
|
|
var signs pb.SignResponse
|
|
if err := signs.Unmarshal(ibtp.Proof); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
threshold := (len(validators.Addresses) - 1) / 3 // TODO be dynamic
|
|
counter := 0
|
|
|
|
ibtpHash := ibtp.Hash()
|
|
hash := sha256.Sum256([]byte(ibtpHash.String()))
|
|
for v, sign := range signs.Sign {
|
|
if _, ok := m[v]; !ok {
|
|
return false, fmt.Errorf("wrong validator: %s", v)
|
|
}
|
|
delete(m, v)
|
|
addr := types.NewAddressByStr(v)
|
|
ok, _ := asym.Verify(crypto.Secp256k1, sign, hash[:], *addr)
|
|
if ok {
|
|
counter++
|
|
}
|
|
if counter > threshold {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, fmt.Errorf("multi signs verify fail, counter: %d", counter)
|
|
}
|
|
|
|
func AppchainKey(id string) string {
|
|
return appchainMgr.PREFIX + id
|
|
}
|
|
|
|
func (x *InterchainManager) indexMapKey(id string) string {
|
|
return fmt.Sprintf("index-tx-%s", id)
|
|
}
|
|
|
|
func (x *InterchainManager) indexReceiptMapKey(id string) string {
|
|
return fmt.Sprintf("index-receipt-tx-%s", id)
|
|
}
|