🆕Create a New Sidechain

When creating a new sidechain, all RPC interfaces should be implemented according to RPC interface specification, and the SPV module should be integrated for block verification. Take DID as an example to illustrate the specific initiating process:

1 . Instance of Initializing the Sidechain Block Storage

Declare the file location of block storage and initialize the block according to the genesis parameters:

ChainStore, err := bc.NewChainStore(activeNetParams.GenesisBlock,
   filepath.Join(DataPath, DataDir, ChainDir))

2. Initialize the SPV Module and Trading Pits

The SPV module is mainly used to synchronize transactions with the mainchain and scan transactions subscribed by the mainchain for processing:

spvCfg := spv.Config{
   DataDir:        filepath.Join(DataPath, DataDir, SpvDir), 
   ChainParams:    spvNetParams,
   PermanentPeers: cfg.SPVPermanentPeers,
   GenesisAddress: genesisAddress,
   FilterType:     filter.FTCustomID,
   NodeVersion:    nodePrefix + Version,
}
   spvService, err := spv.NewService(&spvCfg)

3. Start the P2P Network

Data between sidechain nodes is synchronized through the P2P network:

server, err := server.New(&server.Config{   
   DataDir:        filepath.Join(DataPath, DataDir),
   Chain:          chain, 
   TxMemPool:      txPool,
   ChainParams:    activeNetParams, 
   PermanentPeers: cfg.PermanentPeers, 
   NodeVersion:    nodePrefix + Version,
})

4. SubmitAuxBlockWithBlock

The following is an example of the PoW consensus. SubmitAuxBlockWithBlock is how the sidechain can import the HashRate of the mainchain without HashRate support:

powService := pow.NewService(&powCfg)
go powService.Start()
spvBlockListener.RegisterFunc(powService.SubmitAuxBlockWithBlock)

PoS consensus is recommended.

5. Start RPC and Other Sidechain Services

httpService := sv.NewHttpService(&serviceCfg)
rpcServer := newRPCServer(cfg.RPCPort, httpService)

6. The Sidechain Interface needs to Implement the RPC Core Interface

Take some interfaces that implement Golang language as examples.

getblock: query block information through hash

func (s *HttpService) GetBlockByHash(param http.Params) (interface{}, error) {
	str, ok := param.String("blockhash")
	if !ok {
		return nil, http.NewError(int(InvalidParams), "block hash not found")
	}

	var hash common.Uint256
	hashBytes, err := FromReversedString(str)
	if err != nil {
		return nil, http.NewError(int(InvalidParams), "invalid block hash")
	}
	if err := hash.Deserialize(bytes.NewReader(hashBytes)); err != nil {
		return nil, http.NewError(int(InvalidParams), "invalid block hash")
	}

	verbosity, ok := param.Uint("verbosity")
	if !ok {
		verbosity = 1
	}

	return s.getBlock(hash, verbosity)
}

getcurrentheight: get the block height

func (b *BlockChain) GetBestHeight() uint32 {  
    return b.db.GetHeight()
}

getblockhash: get block hash through height

func (s *HttpService) GetBlockHash(param http.Params) (interface{}, error) {
	height, ok := param.Uint32("height")
	if !ok {
		return nil, http.NewError(int(InvalidParams), " height parameter should be a positive integer")
	}

	hash, err := s.cfg.Chain.GetBlockHash(height)
	if err != nil {
		return nil, newError(InvalidParams)
	}
	return ToReversedString(hash), nil
}

getrawmempool: get memory pool transaction

func (s *HttpService) GetTransactionPool(param http.Params) (interface{}, error) {
	txs := make([]*TransactionInfo, 0)
	for _, t := range s.cfg.TxMemPool.GetTxsInPool() {
		txs = append(txs, s.cfg.GetTransactionInfo(s.cfg, nil, t))
	}
	return txs, nil
}

getrawtransaction: get transaction information by transaction ID

func (s *HttpService) GetRawTransaction(param http.Params) (interface{}, error) {
	str, ok := param.String("txid")
	if !ok {
		return nil, newError(InvalidParams)
	}

	hex, err := FromReversedString(str)
	if err != nil {
		return nil, newError(InvalidParams)
	}
	var hash common.Uint256
	err = hash.Deserialize(bytes.NewReader(hex))
	if err != nil {
		return nil, newError(InvalidParams)
	}
	var header interfaces.Header
	tx, height, err := s.cfg.Chain.GetTransaction(hash)
	if err != nil {
		//try to find transaction in transaction pool.
		tx = s.cfg.TxMemPool.GetTransaction(hash)
		if tx == nil {
			return nil, newError(UnknownTransaction)
		}
	} else {
		bHash, err := s.cfg.Chain.GetBlockHash(height)
		if err != nil {
			return nil, newError(UnknownTransaction)
		}
		header, err = s.cfg.Chain.GetHeader(bHash)
		if err != nil {
			return nil, newError(UnknownTransaction)
		}
	}

	verbose, ok := param.Bool("verbose")
	if verbose {
		return s.cfg.GetTransactionInfo(s.cfg, header, tx), nil
	} else {
		buf := new(bytes.Buffer)
		tx.Serialize(buf)
		return common.BytesToHexString(buf.Bytes()), nil
	}
}

getblockcount: get the next block height

func (s *HttpService) GetBlockCount(param http.Params) (interface{}, error) { 
  return s.cfg.Chain.GetBestHeight() + 1, nil
}

getblockbyheight: get blocks by height

func (s *HttpService) GetBlockByHeight(param http.Params) (interface{}, error) {
	height, ok := param.Uint("height")
	if !ok {
		return nil, http.NewError(int(InvalidParams), "height parameter should be a positive integer")
	}

	hash, err := s.cfg.Chain.GetBlockHash(uint32(height))
	if err != nil {
		return nil, newError(UnknownBlock)
	}

	return s.getBlock(hash, 2)
}

getbestblockhash: get the highest block hash

func (s *HttpService) GetBestBlockHash(param http.Params) (interface{}, error) {
	height := s.cfg.Chain.GetBestHeight()
	hash, err := s.cfg.Chain.GetBlockHash(height)
	if err != nil {
		return nil, newError(InvalidParams)
	}
	return ToReversedString(hash), nil
}

sendrechargetransaction: send recharge transaction

func (s *HttpService) SendRechargeToSideChainTxByHash(param http.Params) (interface{}, error) {
	if ok := CheckRPCServiceLevel(s.cfg.ConfigurationPermitted, config.TransactionPermitted); ok != nil {
		return nil, http.NewError(int(InvalidMethod), "requesting method if out of service level")
	}

	txid, ok := param.String("txid")
	if !ok {
		return nil, http.NewError(int(InvalidParams), "txid not found")
	}

	txBytes, err := common.HexStringToBytes(txid)
	if err != nil {
		return nil, http.NewError(int(InvalidParams), "invalid txid")
	}

	hash, err := common.Uint256FromBytes(txBytes)
	if err != nil {
		return nil, http.NewError(int(InvalidParams), "to tx hash failed")
	}

	tx, err := s.cfg.SpvService.GetTransaction(hash)
	if err != nil {
		return nil, http.NewError(int(InvalidParams), "invalid tx hash")
	}

	depositTx, err := createRechargeToSideChainTransaction(tx, s.cfg.GenesisAddress)
	if err != nil {
		return nil, http.NewError(int(InvalidParams), "create recharge tx failed")
	}

	if err := s.verifyAndSendTx(depositTx); err != nil {
		return nil, ruleError(err)
	}
	return depositTx.Hash().String(), nil
}

sendrawtransaction: send transaction

func (s *HttpService) SendRawTransaction(param http.Params) (interface{}, error) {
	if ok := CheckRPCServiceLevel(s.cfg.ConfigurationPermitted, config.TransactionPermitted); ok != nil {
		return nil, http.NewError(int(InvalidMethod), "requesting method if out of service level")
	}

	str, ok := param.String("data")
	if !ok {
		return nil, http.NewError(int(InvalidParams), "need a string parameter named data")
	}

	bys, err := common.HexStringToBytes(str)
	if err != nil {
		return nil, http.NewError(int(InvalidParams), "hex string to bytes error:"+err.Error())
	}
	var txn types.Transaction
	if err := txn.Deserialize(bytes.NewReader(bys)); err != nil {
		return nil, http.NewError(int(InvalidTransaction), "transaction deserialize error:"+err.Error())
	}

	if err := s.verifyAndSendTx(&txn); err != nil {
		return nil, ruleError(err)
	}

	return ToReversedString(txn.Hash()), nil
}

getwithdrawtransaction: get withdrawal transaction

func (s *HttpService) GetWithdrawTransactionByHash(param http.Params) (interface{}, error) {
	str, ok := param.String("txid")
	if !ok {
		return nil, http.NewError(int(InvalidParams), "txid not found")
	}
	hex, err := FromReversedString(str)
	if err != nil {
		return nil, http.NewError(int(InvalidParams), "txid reverse failed")
	}
	var hash common.Uint256
	err = hash.Deserialize(bytes.NewReader(hex))
	if err != nil {
		return nil, http.NewError(int(InvalidTransaction), "txid deserialize failed")
	}
	tx, _, err := s.cfg.Chain.GetTransaction(hash)
	if err != nil {
		return nil, http.NewError(int(UnknownTransaction), "get tx by txid failed")
	}
	payload, ok := tx.Payload.(*types.PayloadTransferCrossChainAsset)
	if !ok {
		return nil, http.NewError(int(UnknownTransaction), "get tx by txid failed")
	}

	var txOuputsInfo []*WithdrawOutputInfo
	for i := 0; i < len(payload.CrossChainAmounts); i++ {
		txOuputsInfo = append(txOuputsInfo, &WithdrawOutputInfo{
			CrossChainAddress: payload.CrossChainAddresses[i],
			CrossChainAmount:  payload.CrossChainAmounts[i].String(),
			OutputAmount:      tx.Outputs[payload.OutputIndexes[i]].Value.String(),
		})
	}

	txWithdraw := WithdrawTxInfo{
		TxID:             ToReversedString(tx.Hash()),
		CrossChainAssets: txOuputsInfo,
	}

	return txWithdraw, nil
}

getwithdrawtransactionsbyweight: get the withdrawal transaction at a certain height

func (s *HttpService) GetWithdrawTransactionsByHeight(param http.Params) (interface{}, error) {
	height, ok := param.Uint("height")
	if !ok {
		return nil, http.NewError(int(InvalidParams), "height parameter should be a positive integer")
	}

	hash, err := s.cfg.Chain.GetBlockHash(uint32(height))
	if err != nil {
		return nil, newError(UnknownBlock)

	}
	block, err := s.cfg.Chain.GetBlockByHash(hash)
	if err != nil {
		return nil, newError(UnknownBlock)
	}

	destroyHash := common.Uint168{}
	txs := s.GetBlockTransactionsDetail(block, func(tran *types.Transaction) bool {
		_, ok := tran.Payload.(*types.PayloadTransferCrossChainAsset)
		if !ok {
			return false
		}
		for _, output := range tran.Outputs {
			if output.ProgramHash == destroyHash {
				return true
			}
		}
		return false
	})
	return s.GetWithdrawTxsInfo(txs), nil
}

getexistdeposittransactions: get the existing recharge transaction

func (s *HttpService) GetExistDepositTransactions(param http.Params) (interface{}, error) {
	txs, ok := GetStringArray(param, "txs")
	if !ok {
		return nil, http.NewError(int(InvalidParams), "txs not found")
	}

	var resultTxHashes []string
	for _, txHash := range txs {
		txHashBytes, err := common.HexStringToBytes(txHash)
		if err != nil {
			return nil, newError(InvalidParams)
		}
		hash, err := common.Uint256FromBytes(txHashBytes)
		if err != nil {
			return nil, newError(InvalidParams)
		}
		inStore := s.cfg.Chain.IsDuplicateMainchainTx(*hash)
		inTxPool := s.cfg.TxMemPool.IsDuplicateMainChainTx(*hash)
		if inTxPool || inStore {
			resultTxHashes = append(resultTxHashes, txHash)
		}
	}

	return resultTxHashes, nil
}

getillegalevidencebyheight: get the evidence of illegal actions at a certain height

func (s *HttpService) GetIllegalEvidenceByHeight(param http.Params) (interface{}, error) {
	height, ok := param.Uint("height")
	if !ok {
		return nil, http.NewError(int(InvalidParams), "height parameter should be a positive integer")
	}

	hash, err := s.cfg.Chain.GetBlockHash(uint32(height))
	if err != nil {
		return nil, newError(UnknownBlock)

	}
	_, err = s.cfg.Chain.GetBlockByHash(hash)
	if err != nil {
		return nil, newError(UnknownBlock)
	}

	// todo get illegal evidences in block

	result := make([]*SidechainIllegalDataInfo, 0)
	return result, nil
}

checkillegalevidence: check evidence of illegal actions

func (s *HttpService) CheckIllegalEvidence(param http.Params) (interface{}, error) {
	evidence, ok := param["evidence"]
	if !ok {
		return nil, http.NewError(int(InvalidParams), "no evidence")
	}
	e := new(SidechainIllegalDataInfo)
	if err := Unmarshal(evidence, e); err != nil {
		log.Error("[CheckIllegalEvidence] received invalid evidence")
		return false, err
	}

	// todo check illegal evidence

	return false, nil
}

Last updated