diff --git a/.gitignore b/.gitignore
index 0e6068efa..dab78c775 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
 # XXX: Project Related
-chains
+ain_blockchain_data
 logs
 log1.txt
 log2.txt
diff --git a/blockchain/block-file-util.js b/blockchain/block-file-util.js
deleted file mode 100644
index 72c7632d3..000000000
--- a/blockchain/block-file-util.js
+++ /dev/null
@@ -1,109 +0,0 @@
-const fs = require('fs');
-const glob = require('glob');
-const path = require('path');
-const {compare} = require('natural-orderby');
-const zlib = require('zlib');
-const {CHAINS_N2B_DIR_NAME, CHAINS_H2N_DIR_NAME} = require('../common/constants');
-const ChainUtil = require('../common/chain-util');
-const FILE_NAME_SUFFIX = 'json.gz';
-const logger = require('../logger')('BLOCK-FILE-UTIL');
-
-class BlockFileUtil {
-  static getBlockPath(chainPath, blockNumber) {
-    return path.join(chainPath, CHAINS_N2B_DIR_NAME, this.getBlockFilenameByNumber(blockNumber));
-  }
-
-  static getHashToNumberPath(chainPath, blockHash) {
-    return path.join(chainPath, CHAINS_H2N_DIR_NAME, blockHash);
-  }
-
-  static getBlockFilenameByNumber(blockNumber) {
-    return `${blockNumber}.${FILE_NAME_SUFFIX}`;
-  }
-
-  static getBlockFilename(block) {
-    return this.getBlockFilenameByNumber(block.number);
-  }
-
-  // TODO(cshcomcom): Don't use glob?
-  static getAllBlockPaths(chainPath) {
-    const allBlockFilesPattern = `${chainPath}/${CHAINS_N2B_DIR_NAME}/*.${FILE_NAME_SUFFIX}`;
-    return glob.sync(allBlockFilesPattern).sort(compare());
-  }
-
-  static getBlockPaths(chainPath, from, to) {
-    const blockPaths = [];
-    for (let number = from; number < to; number++) {
-      const blockFile = this.getBlockPath(chainPath, number);
-      if (fs.existsSync(blockFile)) {
-        blockPaths.push(blockFile);
-      }
-    }
-    return blockPaths;
-  }
-
-  static createBlockchainDir(chainPath) {
-    const n2bPath = path.join(chainPath, CHAINS_N2B_DIR_NAME);
-    const h2nPath = path.join(chainPath, CHAINS_H2N_DIR_NAME);
-    let isBlockEmpty = true;
-
-    if (!fs.existsSync(chainPath)) {
-      fs.mkdirSync(chainPath, {recursive: true});
-    }
-
-    if (!fs.existsSync(n2bPath)) {
-      fs.mkdirSync(n2bPath);
-    }
-
-    if (!fs.existsSync(h2nPath)) {
-      fs.mkdirSync(h2nPath);
-    }
-
-    if (fs.readdirSync(n2bPath).length > 0) {
-      isBlockEmpty = false;
-    }
-    return isBlockEmpty;
-  }
-
-  // TODO(cshcomcom): Change to asynchronous.
-  static readBlock(blockPath) {
-    const zippedFs = fs.readFileSync(blockPath);
-    return JSON.parse(zlib.gunzipSync(zippedFs).toString());
-  }
-
-  static readBlockByNumber(chainPath, blockNumber) {
-    const blockPath = this.getBlockPath(chainPath, blockNumber);
-    return this.readBlock(blockPath);
-  }
-
-  // TODO(cshcomcom): Change to asynchronous.
-  static writeBlock(chainPath, block) {
-    const blockPath = this.getBlockPath(chainPath, block.number);
-    if (!fs.existsSync(blockPath)) {
-      const compressed = zlib.gzipSync(Buffer.from(JSON.stringify(block)));
-      fs.writeFileSync(blockPath, compressed);
-    } else {
-      logger.debug(`${blockPath} file already exists!`);
-    }
-  }
-
-  static writeHashToNumber(chainPath, blockHash, blockNumber) {
-    if (!blockHash || !ChainUtil.isNumber(blockNumber) || blockNumber < 0) {
-      logger.error(`Invalid writeHashToNumber parameters (${blockHash}, ${blockNumber})`);
-      return;
-    }
-    const hashToNumberPath = this.getHashToNumberPath(chainPath, blockHash);
-    if (!fs.existsSync(hashToNumberPath)) {
-      fs.writeFileSync(hashToNumberPath, blockNumber);
-    } else {
-      logger.debug(`${hashToNumberPath} file already exists!`);
-    }
-  }
-
-  static readHashToNumber(chainPath, blockHash) {
-    const hashToNumberPath = this.getHashToNumberPath(chainPath, blockHash);
-    return Number(fs.readFileSync(hashToNumberPath).toString());
-  }
-}
-
-module.exports = BlockFileUtil;
diff --git a/blockchain/block.js b/blockchain/block.js
index 0ef476d41..da6bac52a 100644
--- a/blockchain/block.js
+++ b/blockchain/block.js
@@ -1,6 +1,7 @@
 const stringify = require('fast-json-stable-stringify');
 const sizeof = require('object-sizeof');
 const moment = require('moment');
+const _ = require('lodash');
 const logger = require('../logger')('BLOCK');
 const ChainUtil = require('../common/chain-util');
 const Transaction = require('../tx-pool/transaction');
@@ -259,7 +260,6 @@ class Block {
   }
 
   static buildGenesisStakingTxs(timestamp) {
-    const _ = require('lodash');
     const txs = [];
     Object.entries(GENESIS_VALIDATORS).forEach(([address, amount], index) => {
       const privateKey = _.get(GenesisAccounts,
diff --git a/blockchain/index.js b/blockchain/index.js
index 32d9e3127..2292f5aac 100644
--- a/blockchain/index.js
+++ b/blockchain/index.js
@@ -3,8 +3,11 @@ const path = require('path');
 const fs = require('fs');
 const logger = require('../logger')('BLOCKCHAIN');
 const { Block } = require('./block');
-const BlockFileUtil = require('./block-file-util');
-const { CHAINS_DIR } = require('../common/constants');
+const FileUtil = require('../common/file-util');
+const {
+  CHAINS_DIR,
+  CHAINS_N2B_DIR_NAME,
+} = require('../common/constants');
 const CHAIN_SEGMENT_LENGTH = 20;
 const ON_MEM_CHAIN_LENGTH = 20;
 
@@ -13,11 +16,13 @@ class Blockchain {
     // Finalized chain
     this.chain = [];
     this.blockchainPath = path.resolve(CHAINS_DIR, basePath);
+    this.initSnapshotBlockNumber = -1;
   }
 
-  init(isFirstNode) {
+  init(isFirstNode, latestSnapshotBlockNumber) {
     let lastBlockWithoutProposal;
-    if (BlockFileUtil.createBlockchainDir(this.blockchainPath)) {
+    this.initSnapshotBlockNumber = latestSnapshotBlockNumber;
+    if (FileUtil.createBlockchainDir(this.blockchainPath)) {
       if (isFirstNode) {
         logger.info('\n');
         logger.info('############################################################');
@@ -48,18 +53,23 @@ class Blockchain {
         logger.info('################################################################');
         logger.info('\n');
       }
-      const newChain = this.loadChain();
+      const newChain = this.loadChain(latestSnapshotBlockNumber);
       if (newChain) {
         // NOTE(minsulee2): Deal with the case the only genesis block was generated.
         if (newChain.length > 1) {
           lastBlockWithoutProposal = newChain.pop();
-          const lastBlockPath = BlockFileUtil.getBlockPath(
+          const lastBlockPath = FileUtil.getBlockPath(
               this.blockchainPath, lastBlockWithoutProposal.number);
           fs.unlinkSync(lastBlockPath);
         }
         this.chain = newChain;
       }
     }
+    if (!this.getBlockByNumber(0)) {
+      const genesisBlock = Block.genesis();
+      FileUtil.writeBlock(this.blockchainPath, genesisBlock);
+      FileUtil.writeHashToNumber(this.blockchainPath, genesisBlock.hash, genesisBlock.number);
+    }
     return lastBlockWithoutProposal;
   }
 
@@ -72,13 +82,12 @@ class Blockchain {
     */
   getBlockByHash(hash) {
     if (!hash) return null;
-    const blockPath = BlockFileUtil.getBlockPath(this.blockchainPath,
-        BlockFileUtil.readHashToNumber(this.blockchainPath, hash));
-    if (blockPath === undefined) {
-      const found = this.chain.filter((block) => block.hash === hash);
-      return found.length ? found[0] : null;
+    const blockPath = FileUtil.getBlockPath(this.blockchainPath,
+        FileUtil.readHashToNumber(this.blockchainPath, hash));
+    if (!blockPath) {
+      return this.chain.find((block) => block.hash === hash);
     } else {
-      return Block.parse(BlockFileUtil.readBlock(blockPath));
+      return Block.parse(FileUtil.readCompressedJson(blockPath));
     }
   }
 
@@ -90,12 +99,11 @@ class Blockchain {
     */
   getBlockByNumber(number) {
     if (number === undefined || number === null) return null;
-    const blockPath = BlockFileUtil.getBlockPath(this.blockchainPath, number);
-    if (blockPath === undefined || number > this.lastBlockNumber() - ON_MEM_CHAIN_LENGTH) {
-      const found = this.chain.filter((block) => block.number === number);
-      return found.length ? found[0] : null;
+    const blockPath = FileUtil.getBlockPath(this.blockchainPath, number);
+    if (!blockPath || number > this.lastBlockNumber() - ON_MEM_CHAIN_LENGTH) {
+      return this.chain.find((block) => block.number === number);
     } else {
-      return Block.parse(BlockFileUtil.readBlock(blockPath));
+      return Block.parse(FileUtil.readCompressedJson(blockPath));
     }
   }
 
@@ -109,6 +117,9 @@ class Blockchain {
   lastBlockNumber() {
     const lastBlock = this.lastBlock();
     if (!lastBlock) {
+      if (this.initSnapshotBlockNumber) {
+        return this.initSnapshotBlockNumber;
+      }
       return -1;
     }
     return lastBlock.number;
@@ -153,21 +164,31 @@ class Blockchain {
     return true;
   }
 
-  static isValidChain(chain) {
+  static isValidChain(chain, latestSnapshotBlockNumber) {
+    if (!chain.length) {
+      return true;
+    }
     const firstBlock = Block.parse(chain[0]);
-    if (!firstBlock || firstBlock.hash !== Block.genesis().hash) {
-      logger.error(`First block is not the Genesis block: ${firstBlock}\n${Block.genesis()}`);
+    if (!firstBlock) {
+      return false;
+    }
+    if (latestSnapshotBlockNumber > 0 && latestSnapshotBlockNumber + 1 !== firstBlock.number) {
+      logger.error(`Missing blocks between ${latestSnapshotBlockNumber + 1} and ${firstBlock.number}`);
       return false;
     }
-    if (!Block.validateHashes(firstBlock)) {
-      logger.error(`Genesis block is corrupted`);
+    if (firstBlock.number === 0 && firstBlock.hash !== Block.genesis().hash) {
+      logger.error(`Invalid genesis block: ${firstBlock}\n${Block.genesis()}`);
       return false;
     }
-    // TODO(liayoo): Check if the tx nonces are correct.
     return Blockchain.isValidChainSegment(chain);
   }
 
   static isValidChainSegment(chainSegment) {
+    if (chainSegment.length) {
+      if (!Block.validateHashes(chainSegment[0])) {
+        return false;
+      }
+    }
     for (let i = 1; i < chainSegment.length; i++) {
       const block = chainSegment[i];
       const lastBlock = Block.parse(chainSegment[i - 1]);
@@ -181,52 +202,16 @@ class Blockchain {
   writeChain() {
     for (let i = 0; i < this.chain.length; i++) {
       const block = this.chain[i];
-      BlockFileUtil.writeBlock(this.blockchainPath, block);
-      BlockFileUtil.writeHashToNumber(this.blockchainPath, block.hash, block.number);
-    }
-  }
-
-  /**
-    * Returns a section of the chain up to a maximuim of length CHAIN_SEGMENT_LENGTH, starting from
-    * the block number of the reference block.
-    *
-    * @param {Block} refBlock - The current highest block tin the querying nodes blockchain
-    * @return {list} A list of Block instances with refBlock at index 0, up to a maximuim length
-    *                CHAIN_SEGMENT_LENGTH
-    */
-  requestBlockchainSection(refBlock) {
-    const refBlockNumber = refBlock ? refBlock.number : -1;
-    const nextBlockNumber = refBlockNumber + 1;
-
-    logger.info(`Current last block number: ${this.lastBlockNumber()}, ` +
-                `Requester's last block number: ${refBlockNumber}`);
-
-    const blockPaths = BlockFileUtil.getBlockPaths(this.blockchainPath, nextBlockNumber, nextBlockNumber + CHAIN_SEGMENT_LENGTH);
-
-    if (blockPaths.length > 0 &&
-        (!!(refBlock) && Block.parse(BlockFileUtil.readBlock(blockPaths[0])).last_hash !== refBlock.hash)) {
-      logger.error('Invalid blockchain request. Requesters last block does not belong to ' +
-          'this blockchain');
-      return;
+      FileUtil.writeBlock(this.blockchainPath, block);
+      FileUtil.writeHashToNumber(this.blockchainPath, block.hash, block.number);
     }
-
-    const refBlockHash = refBlock ? refBlock.hash : null;
-    if (refBlockHash === this.lastBlock().hash) {
-      logger.info(`Requesters blockchain is up to date with this blockchain`);
-      return [this.lastBlock()];
-    }
-
-    const chainSegment = [];
-    blockPaths.forEach((blockFile) => {
-      chainSegment.push(Block.parse(BlockFileUtil.readBlock(blockFile)));
-    });
-    return chainSegment.length > 0 ? chainSegment : [];
   }
 
-  getValidBlocks(chainSegment) {
+  getValidBlocksInChainSegment(chainSegment) {
     logger.info(`Last block number before merge: ${this.lastBlockNumber()}`);
     const firstBlock = Block.parse(chainSegment[0]);
-    const lastBlockHash = this.lastBlockNumber() >= 0 ? this.lastBlock().hash : null;
+    const lastBlock = this.lastBlock();
+    const lastBlockHash = this.lastBlockNumber() >= 0 && lastBlock ? lastBlock.hash : null;
     const overlap = lastBlockHash ?
         chainSegment.filter((block) => block.number === this.lastBlockNumber()) : null;
     const overlappingBlock = overlap ? overlap[0] : null;
@@ -234,13 +219,13 @@ class Blockchain {
     if (lastBlockHash) {
       // Case 1: Not a cold start.
       if (overlappingBlock && overlappingBlock.hash !== lastBlockHash) {
-        logger.info(`The last block's hash ${this.lastBlock().hash.substring(0, 5)} ` +
+        logger.info(`The last block's hash ${lastBlock.hash.substring(0, 5)} ` +
             `does not match with the first block's hash ${firstBlock.hash.substring(0, 5)}`);
         return validBlocks;
       }
     } else {
       // Case 2: A cold start.
-      if (firstBlock.last_hash !== '') {
+      if (firstBlock.number === 0 && firstBlock.last_hash !== '') {
         logger.info(`First block of hash ${firstBlock.hash.substring(0, 5)} ` +
             `and last hash ${firstBlock.last_hash.substring(0, 5)} is not a genesis block`);
         return validBlocks;
@@ -259,17 +244,24 @@ class Blockchain {
     return validBlocks;
   }
 
-  loadChain() {
+  /**
+   * Reads the block files at the chains n2b directory and returns a list of blocks starting from
+   * the latestSnapshotBlockNumber + 1.
+   * @param {Number} latestSnapshotBlockNumber
+   * @returns {list} A list of Blocks
+   */
+  loadChain(latestSnapshotBlockNumber) {
     const chainPath = this.blockchainPath;
     const newChain = [];
-    const blockPaths = BlockFileUtil.getAllBlockPaths(chainPath);
+    const numBlockFiles = fs.readdirSync(path.join(chainPath, CHAINS_N2B_DIR_NAME)).length;
+    const blockPaths = FileUtil.getBlockPaths(chainPath, latestSnapshotBlockNumber + 1, numBlockFiles);
 
     blockPaths.forEach((blockPath) => {
-      const block = Block.parse(BlockFileUtil.readBlock(blockPath));
+      const block = Block.parse(FileUtil.readCompressedJson(blockPath));
       newChain.push(block);
     });
 
-    if (Blockchain.isValidChain(newChain)) {
+    if (Blockchain.isValidChain(newChain, latestSnapshotBlockNumber)) {
       logger.info(`Valid chain of size ${newChain.length}`);
       return newChain;
     }
@@ -278,7 +270,25 @@ class Blockchain {
     return null;
   }
 
-  getChainSection(from, to) {
+  /**
+    * Returns a section of the chain up to a maximuim of length CHAIN_SEGMENT_LENGTH, starting from
+    * the `from` block number up till `to` block number.
+    *
+    * @param {Number} from - The lowest block number to get
+    * @param {Number} to - The highest block number to geet
+    * @return {list} A list of Blocks, up to a maximuim length of CHAIN_SEGMENT_LENGTH
+    */
+  getBlockList(from, to) {
+    const blockList = [];
+    const lastBlock = this.lastBlock();
+    if (!lastBlock) {
+      return blockList;
+    }
+    if (from === lastBlock.number + 1) {
+      logger.info(`Requesters blockchain is up to date with this blockchain`);
+      blockList.push(lastBlock);
+      return blockList;
+    }
     if (!Number.isInteger(from) || from < 0) {
       from = 0;
     }
@@ -288,13 +298,11 @@ class Blockchain {
     if (to - from > CHAIN_SEGMENT_LENGTH) { // NOTE: To prevent large query.
       to = from + CHAIN_SEGMENT_LENGTH;
     }
-    const chain = [];
-    const blockPaths = BlockFileUtil.getBlockPaths(this.blockchainPath, from, to);
+    const blockPaths = FileUtil.getBlockPaths(this.blockchainPath, from, to - from);
     blockPaths.forEach((blockPath) => {
-      const block = Block.parse(BlockFileUtil.readBlock(blockPath));
-      chain.push(block);
+      blockList.push(Block.parse(FileUtil.readCompressedJson(blockPath)));
     });
-    return chain;
+    return blockList;
   }
 }
 
diff --git a/client/index.js b/client/index.js
index 9228d6eef..aafcad09f 100755
--- a/client/index.js
+++ b/client/index.js
@@ -298,7 +298,7 @@ app.get('/connection_status', (req, res) => {
 app.get('/blocks', (req, res, next) => {
   const blockEnd = node.bc.lastBlockNumber() + 1;
   const blockBegin = Math.max(blockEnd - MAX_BLOCKS, 0);
-  const result = node.bc.getChainSection(blockBegin, blockEnd);
+  const result = node.bc.getBlockList(blockBegin, blockEnd);
   res.status(200)
     .set('Content-Type', 'application/json')
     .send({code: 0, result})
@@ -384,7 +384,9 @@ app.get('/get_transaction', (req, res, next) => {
     if (transactionInfo.status === TransactionStatus.BLOCK_STATUS) {
       const block = node.bc.getBlockByNumber(transactionInfo.number);
       const index = transactionInfo.index;
-      if (index >= 0) {
+      if (!block) {
+        logger.debug(`No block found for the tx: ${req.query.hash}`);
+      } else if (index >= 0) {
         transactionInfo.transaction = block.transactions[index];
       } else {
         transactionInfo.transaction = _.find(block.last_votes, (tx) => tx.hash === req.query.hash);
@@ -478,6 +480,9 @@ function createSingleSetTxBody(input, opType) {
   if (input.gas_price !== undefined) {
     txBody.gas_price = input.gas_price;
   }
+  if (input.billing !== undefined) {
+    txBody.billing = input.billing;
+  }
   return txBody;
 }
 
@@ -500,6 +505,9 @@ function createMultiSetTxBody(input) {
   if (input.gas_price !== undefined) {
     txBody.gas_price = input.gas_price;
   }
+  if (input.billing !== undefined) {
+    txBody.billing = input.billing;
+  }
   return txBody;
 }
 
diff --git a/client/protocol_versions.json b/client/protocol_versions.json
index 76a8660de..5942ecf05 100644
--- a/client/protocol_versions.json
+++ b/client/protocol_versions.json
@@ -43,5 +43,8 @@
   },
   "0.7.6": {
     "min": "0.7.0"
+  },
+  "0.7.7": {
+    "min": "0.7.0"
   }
 }
\ No newline at end of file
diff --git a/common/chain-util.js b/common/chain-util.js
index 9757d7725..8f1feef8f 100644
--- a/common/chain-util.js
+++ b/common/chain-util.js
@@ -1,5 +1,3 @@
-const EC = require('elliptic').ec;
-const ec = new EC('secp256k1');
 const stringify = require('fast-json-stable-stringify');
 const ainUtil = require('@ainblockchain/ain-util');
 const _ = require('lodash');
@@ -72,18 +70,6 @@ class ChainUtil {
     return address;
   }
 
-  // TODO(liayoo): Remove this function.
-  static genKeyPair() {
-    let keyPair;
-    if (PRIVATE_KEY) {
-      keyPair = ec.keyFromPrivate(PRIVATE_KEY, 'hex');
-      keyPair.getPublic();
-    } else {
-      keyPair = ec.genKeyPair();
-    }
-    return keyPair;
-  }
-
   static isBool(value) {
     return ruleUtil.isBool(value);
   }
@@ -164,6 +150,12 @@ class ChainUtil {
     return ruleUtil.toServiceAccountName(serviceType, serviceName, key);
   }
 
+  // NOTE(liayoo): billing is in the form <app name>|<billing id>
+  static toBillingAccountName(billing) {
+    const { PredefinedDbPaths } = require('../common/constants');
+    return `${PredefinedDbPaths.BILLING}|${billing}`;
+  }
+
   static toEscrowAccountName(source, target, escrowKey) {
     return ruleUtil.toEscrowAccountName(source, target, escrowKey);
   }
@@ -353,13 +345,43 @@ class ChainUtil {
 
   static isAppPath(parsedPath) {
     const { PredefinedDbPaths } = require('../common/constants');
+
     return _.get(parsedPath, 0) === PredefinedDbPaths.APPS;
   }
 
   // TODO(liayoo): Fix testing paths (writing at the root) and update isServicePath().
   static isServicePath(parsedPath) {
-    const { NATIVE_SERVICE_TYPES } = require('../common/constants');
-    return NATIVE_SERVICE_TYPES.includes(_.get(parsedPath, 0));
+    const { isServiceType } = require('../common/constants');
+
+    return isServiceType(_.get(parsedPath, 0));
+  }
+
+  static getDependentAppNameFromRef(ref) {
+    const { isAppDependentServiceType } = require('../common/constants');
+    const parsedPath = ChainUtil.parsePath(ref);
+    const type = _.get(parsedPath, 0);
+    if (!type || !isAppDependentServiceType(type)) {
+      return null;
+    }
+    return _.get(parsedPath, 1, null);
+  }
+
+  static getServiceDependentAppNameList(op) {
+    if (!op) {
+      return [];
+    }
+    if (op.op_list) {
+      const appNames = new Set();
+      for (const innerOp of op.op_list) {
+        const name = ChainUtil.getDependentAppNameFromRef(innerOp.ref);
+        if (name) {
+          appNames.add(name);
+        }
+      }
+      return [...appNames];
+    }
+    const name = ChainUtil.getDependentAppNameFromRef(op.ref);
+    return name ? [name] : [];
   }
 
   static getSingleOpGasAmount(parsedPath, value) {
diff --git a/common/constants.js b/common/constants.js
index 8fb2e6b24..39e18073d 100644
--- a/common/constants.js
+++ b/common/constants.js
@@ -1,10 +1,9 @@
-const os = require('os');
 const fs = require('fs');
 const path = require('path');
 const semver = require('semver');
 const ChainUtil = require('./chain-util');
 
-// Genesis configs.
+// ** Genesis configs **
 const DEFAULT_GENESIS_CONFIGS_DIR = 'genesis-configs/base';
 const CUSTOM_GENESIS_CONFIGS_DIR = process.env.GENESIS_CONFIGS_DIR ?
     process.env.GENESIS_CONFIGS_DIR : null;
@@ -12,7 +11,7 @@ const GenesisParams = getGenesisConfig('genesis_params.json');
 const GenesisToken = getGenesisConfig('genesis_token.json');
 const GenesisAccounts = getGenesisConfig('genesis_accounts.json');
 
-// Feature flags.
+// ** Feature flags **
 // NOTE(platfowner): If there is a corresponding env variable (e.g. force... flags),
 //                   the flag value will be OR-ed to the value.
 const FeatureFlags = {
@@ -30,7 +29,7 @@ const FeatureFlags = {
   enableRichTxSelectionLogging: false,
 };
 
-// Environment variables.
+// ** Environment variables **
 const DEBUG = ChainUtil.convertEnvVarInputToBool(process.env.DEBUG);
 const CONSOLE_LOG = ChainUtil.convertEnvVarInputToBool(process.env.CONSOLE_LOG);
 const ENABLE_DEV_SET_CLIENT_API = ChainUtil.convertEnvVarInputToBool(process.env.ENABLE_DEV_SET_CLIENT_API);
@@ -44,8 +43,9 @@ const ACCOUNT_INDEX = process.env.ACCOUNT_INDEX || null;
 const PORT = process.env.PORT || getPortNumber(8080, 8080);
 const P2P_PORT = process.env.P2P_PORT || getPortNumber(5000, 5000);
 const LIGHTWEIGHT = ChainUtil.convertEnvVarInputToBool(process.env.LIGHTWEIGHT);
+const SYNC_MODE = process.env.SYNC_MODE || 'full';
 
-// Constants
+// ** Constants **
 const CURRENT_PROTOCOL_VERSION = require('../package.json').version;
 if (!semver.valid(CURRENT_PROTOCOL_VERSION)) {
   throw Error('Wrong version format is specified in package.json');
@@ -71,27 +71,17 @@ if (!fs.existsSync(BLOCKCHAIN_DATA_DIR)) {
 const CHAINS_DIR = path.resolve(BLOCKCHAIN_DATA_DIR, 'chains');
 const CHAINS_N2B_DIR_NAME = 'n2b'; // NOTE: Block number to block.
 const CHAINS_H2N_DIR_NAME = 'h2n'; // NOTE: Block hash to block number.
+const SNAPSHOTS_ROOT_DIR = path.resolve(BLOCKCHAIN_DATA_DIR, 'snapshots');
+const SNAPSHOTS_N2S_DIR_NAME = 'n2s'; // NOTE: Block number to snapshot.
+const SNAPSHOTS_INTERVAL_BLOCK_NUMBER = 1000; // NOTE: How often the snapshot is made
+const MAX_NUM_SNAPSHOTS = 10; // NOTE: max number of snapshots to keep
 const HASH_DELIMITER = '#';
 const TX_NONCE_ERROR_CODE = 900;
 const TX_TIMESTAMP_ERROR_CODE = 901;
 const MILLI_AIN = 10**-3; // 1,000 milliain = 1 ain
 const MICRO_AIN = 10**-6; // 1,000,000 microain = 1 ain
-const NATIVE_SERVICE_TYPES = [
-  'accounts',
-  'checkin',
-  'consensus',
-  'escrow',
-  'gas_fee',
-  'manage_app',
-  'payments',
-  'service_accounts',
-  'sharding',
-  'staking',
-  'test',  // NOTE(platfowner): A temporary solution for tests.
-  'transfer',
-];
 
-// Enums
+// ** Enums **
 /**
  * Message types for communication between nodes.
  *
@@ -143,6 +133,7 @@ const PredefinedDbPaths = {
   // Gas fee
   GAS_FEE: 'gas_fee',
   COLLECT: 'collect',
+  BILLING: 'billing',
   // Token
   TOKEN: 'token',
   TOKEN_NAME: 'name',
@@ -325,6 +316,11 @@ const NativeFunctionIds = {
   UPDATE_LATEST_SHARD_REPORT: '_updateLatestShardReport',
 };
 
+function isNativeFunctionId(fid) {
+  const fidList = Object.values(NativeFunctionIds);
+  return fidList.includes(fid);
+}
+
 /**
  * Properties of sharding configs.
  *
@@ -410,6 +406,8 @@ const FunctionResultCode = {
   IN_LOCKUP_PERIOD: 'IN_LOCKUP_PERIOD',
   INSUFFICIENT_BALANCE: 'INSUFFICIENT_BALANCE',
   INTERNAL_ERROR: 'INTERNAL_ERROR',  // Something went wrong but don't know why
+  INVALID_ACCOUNT_NAME: 'INVALID_ACCOUNT_NAME',
+  INVALID_SERVICE_NAME: 'INVALID_SERVICE_NAME',
   SUCCESS: 'SUCCESS',
 };
 
@@ -455,6 +453,67 @@ const GasFeeConstants = {
   REST_FUNCTION_CALL_GAS_AMOUNT: 10,
 };
 
+// ** Lists **
+
+/**
+ * Root labels of service paths.
+ */
+const SERVICE_TYPES = [
+  PredefinedDbPaths.ACCOUNTS,
+  PredefinedDbPaths.CHECKIN,
+  PredefinedDbPaths.ESCROW,
+  PredefinedDbPaths.GAS_FEE,
+  PredefinedDbPaths.MANAGE_APP,
+  PredefinedDbPaths.PAYMENTS,
+  PredefinedDbPaths.SERVICE_ACCOUNTS,
+  PredefinedDbPaths.SHARDING,
+  PredefinedDbPaths.STAKING,
+  PredefinedDbPaths.TRANSFER,
+  'test',  // NOTE(platfowner): A temporary solution for tests.
+];
+
+function isServiceType(type) {
+  return SERVICE_TYPES.includes(type);
+}
+
+/**
+ * Service types allowed to create service accounts.
+ */
+const SERVICE_ACCOUNT_SERVICE_TYPES = [
+  PredefinedDbPaths.BILLING,
+  PredefinedDbPaths.ESCROW,
+  PredefinedDbPaths.GAS_FEE,
+  PredefinedDbPaths.PAYMENTS,
+  PredefinedDbPaths.STAKING,
+];
+
+function isServiceAccountServiceType(type) {
+  return SERVICE_ACCOUNT_SERVICE_TYPES.includes(type);
+}
+
+/**
+ * Service types that are app-dependent.
+ */
+const APP_DEPENDENT_SERVICE_TYPES = [
+  PredefinedDbPaths.MANAGE_APP,
+  PredefinedDbPaths.PAYMENTS,
+  PredefinedDbPaths.STAKING,
+];
+
+function isAppDependentServiceType(type) {
+  return APP_DEPENDENT_SERVICE_TYPES.includes(type);
+}
+
+/**
+ * Sync mode options.
+ *
+ * @enum {string}
+ */
+const SyncModeOptions = {
+  FULL: 'full',
+  FAST: 'fast',
+}
+
 /**
  * Overwriting environment variables.
  * These parameters are defined in genesis_params.json, but if specified as environment variables,
@@ -666,6 +725,10 @@ module.exports = {
   CHAINS_DIR,
   CHAINS_N2B_DIR_NAME,
   CHAINS_H2N_DIR_NAME,
+  SNAPSHOTS_ROOT_DIR,
+  SNAPSHOTS_N2S_DIR_NAME,
+  SNAPSHOTS_INTERVAL_BLOCK_NUMBER,
+  MAX_NUM_SNAPSHOTS,
   DEBUG,
   CONSOLE_LOG,
   ENABLE_DEV_SET_CLIENT_API,
@@ -676,12 +739,12 @@ module.exports = {
   PORT,
   P2P_PORT,
   LIGHTWEIGHT,
+  SYNC_MODE,
   HASH_DELIMITER,
   TX_NONCE_ERROR_CODE,
   TX_TIMESTAMP_ERROR_CODE,
   MICRO_AIN,
   MILLI_AIN,
-  NATIVE_SERVICE_TYPES,
   MessageTypes,
   BlockchainNodeStates,
   PredefinedDbPaths,
@@ -695,6 +758,7 @@ module.exports = {
   ProofProperties,
   StateInfoProperties,
   NativeFunctionIds,
+  isNativeFunctionId,
   ShardingProperties,
   ShardingProtocols,
   TokenExchangeSchemes,
@@ -710,6 +774,10 @@ module.exports = {
   GenesisRules,
   GenesisOwners,
   GasFeeConstants,
+  SyncModeOptions,
+  isServiceType,
+  isServiceAccountServiceType,
+  isAppDependentServiceType,
   buildOwnerPermissions,
   buildRulePermission,
   ...GenesisParams.blockchain,
diff --git a/common/file-util.js b/common/file-util.js
new file mode 100644
index 000000000..20b85b9f3
--- /dev/null
+++ b/common/file-util.js
@@ -0,0 +1,169 @@
+const fs = require('fs');
+const path = require('path');
+const zlib = require('zlib');
+const _ = require('lodash');
+const {
+  CHAINS_N2B_DIR_NAME,
+  CHAINS_H2N_DIR_NAME,
+  SNAPSHOTS_N2S_DIR_NAME,
+} = require('./constants');
+const ChainUtil = require('./chain-util');
+const JSON_GZIP_FILE_EXTENSION = 'json.gz';
+const logger = require('../logger')('FILE-UTIL');
+
+class FileUtil {
+  static getBlockPath(chainPath, blockNumber) {
+    if (blockNumber < 0) return null;
+    return path.join(chainPath, CHAINS_N2B_DIR_NAME, this.getBlockFilenameByNumber(blockNumber));
+  }
+
+  static getSnapshotPathByBlockNumber(snapshotPath, blockNumber) {
+    return path.join(snapshotPath, SNAPSHOTS_N2S_DIR_NAME, this.getBlockFilenameByNumber(blockNumber));
+  }
+
+  static getHashToNumberPath(chainPath, blockHash) {
+    return path.join(chainPath, CHAINS_H2N_DIR_NAME, blockHash);
+  }
+
+  static getBlockFilenameByNumber(blockNumber) {
+    return `${blockNumber}.${JSON_GZIP_FILE_EXTENSION}`;
+  }
+
+  static getBlockFilename(block) {
+    return this.getBlockFilenameByNumber(block.number);
+  }
+
+  static getLatestSnapshotInfo(snapshotPath) {
+    const snapshotPathPrefix = path.join(snapshotPath, SNAPSHOTS_N2S_DIR_NAME);
+    let latestSnapshotPath = null;
+    let latestSnapshotBlockNumber = -1;
+    let files = [];
+    try {
+      files = fs.readdirSync(snapshotPathPrefix);
+    } catch (err) {
+      logger.debug(`Failed to read snapshots: ${err.stack}`);
+      return { latestSnapshotPath, latestSnapshotBlockNumber };
+    }
+    for (const file of files) {
+      const blockNumber = _.get(file.split(`.${JSON_GZIP_FILE_EXTENSION}`), 0);
+      if (blockNumber !== undefined && blockNumber > latestSnapshotBlockNumber) {
+        latestSnapshotPath = path.join(snapshotPathPrefix, file);
+        latestSnapshotBlockNumber = Number(blockNumber);
+      }
+    }
+    return { latestSnapshotPath, latestSnapshotBlockNumber };
+  }
+
+  static getBlockPaths(chainPath, from, size) {
+    const blockPaths = [];
+    if (size <= 0) return blockPaths;
+    for (let number = from; number < from + size; number++) {
+      const blockFile = this.getBlockPath(chainPath, number);
+      if (fs.existsSync(blockFile)) {
+        blockPaths.push(blockFile);
+      } else {
+        logger.debug(`blockFile (${blockFile}) does not exist`);
+        return blockPaths;
+      }
+    }
+    return blockPaths;
+  }
+
+  static createBlockchainDir(chainPath) {
+    const n2bPath = path.join(chainPath, CHAINS_N2B_DIR_NAME);
+    const h2nPath = path.join(chainPath, CHAINS_H2N_DIR_NAME);
+    let isBlockEmpty = true;
+
+    if (!fs.existsSync(chainPath)) {
+      fs.mkdirSync(chainPath, {recursive: true});
+    }
+
+    if (!fs.existsSync(n2bPath)) {
+      fs.mkdirSync(n2bPath);
+    }
+
+    if (!fs.existsSync(h2nPath)) {
+      fs.mkdirSync(h2nPath);
+    }
+
+    if (fs.readdirSync(n2bPath).length > 0) {
+      isBlockEmpty = false;
+    }
+    return isBlockEmpty;
+  }
+
+  static createSnapshotDir(snapshotPath) {
+    if (!fs.existsSync(snapshotPath)) {
+      fs.mkdirSync(snapshotPath, { recursive: true });
+    }
+    if (!fs.existsSync(path.join(snapshotPath, SNAPSHOTS_N2S_DIR_NAME))) {
+      fs.mkdirSync(path.join(snapshotPath, SNAPSHOTS_N2S_DIR_NAME));
+    }
+  }
+
+  // TODO(cshcomcom): Change to asynchronous.
+  static readCompressedJson(blockPath) {
+    try {
+      const zippedFs = fs.readFileSync(blockPath);
+      return JSON.parse(zlib.gunzipSync(zippedFs).toString());
+    } catch (err) {
+      return null;
+    }
+  }
+
+  static readBlockByNumber(chainPath, blockNumber) {
+    const blockPath = this.getBlockPath(chainPath, blockNumber);
+    return this.readCompressedJson(blockPath);
+  }
+
+  // TODO(cshcomcom): Change to asynchronous.
+  static writeBlock(chainPath, block) {
+    const blockPath = this.getBlockPath(chainPath, block.number);
+    if (!fs.existsSync(blockPath)) {
+      const compressed = zlib.gzipSync(Buffer.from(JSON.stringify(block)));
+      fs.writeFileSync(blockPath, compressed);
+    } else {
+      logger.debug(`${blockPath} file already exists!`);
+    }
+  }
+
+  static writeHashToNumber(chainPath, blockHash, blockNumber) {
+    if (!blockHash || !ChainUtil.isNumber(blockNumber) || blockNumber < 0) {
+      logger.error(`Invalid writeHashToNumber parameters (${blockHash}, ${blockNumber})`);
+      return;
+    }
+    const hashToNumberPath = this.getHashToNumberPath(chainPath, blockHash);
+    if (!fs.existsSync(hashToNumberPath)) {
+      fs.writeFileSync(hashToNumberPath, blockNumber);
+    } else {
+      logger.debug(`${hashToNumberPath} file already exists!`);
+    }
+  }
+
+  static readHashToNumber(chainPath, blockHash) {
+    try {
+      const hashToNumberPath = this.getHashToNumberPath(chainPath, blockHash);
+      return Number(fs.readFileSync(hashToNumberPath).toString());
+    } catch (err) {
+      return -1;
+    }
+  }
+
+  static writeSnapshot(snapshotPath, blockNumber, snapshot) {
+    const filePath = this.getSnapshotPathByBlockNumber(snapshotPath, blockNumber);
+    if (snapshot === null) { // Delete
+      if (fs.existsSync(filePath)) {
+        try {
+          fs.unlinkSync(filePath);
+        } catch (err) {
+          logger.debug(`Failed to delete ${filePath}: ${err.stack}`);
+        }
+      }
+    } else {
+      // TODO(liayoo): Change this operation to be asynchronous
+      fs.writeFileSync(filePath, zlib.gzipSync(Buffer.from(JSON.stringify(snapshot))));
+    }
+  }
+}
+
+module.exports = FileUtil;
diff --git a/db/functions.js b/db/functions.js
index 3461f3fe8..daf14abc1 100644
--- a/db/functions.js
+++ b/db/functions.js
@@ -84,14 +84,6 @@ class Functions {
     this.callStack = [];
   }
 
-  static isNativeFunctionId(fid) {
-    if (!fid) {
-      return false;
-    }
-    const fidList = Object.values(NativeFunctionIds);
-    return fidList.find((elem) => elem === fid) !== undefined;
-  }
-
   /**
    * Runs functions of function paths matched with given database path.
    *
@@ -545,6 +537,8 @@ class Functions {
   }
 
   _createApp(value, context) {
+    const { isValidServiceName } = require('./state-util');
+
     const appName = context.params.app_name;
     const recordId = context.params.record_id;
     const resultPath = PathUtil.getCreateAppResultPath(appName, recordId);
@@ -552,6 +546,10 @@ class Functions {
     const adminConfig = value[PredefinedDbPaths.MANAGE_APP_CONFIG_ADMIN];
     const billingConfig = _.get(value, PredefinedDbPaths.MANAGE_APP_CONFIG_BILLING);
     const serviceConfig = _.get(value, PredefinedDbPaths.MANAGE_APP_CONFIG_SERVICE);
+    if (!isValidServiceName(appName)) {
+      return this.saveAndReturnFuncResult(
+          context, resultPath, FunctionResultCode.INVALID_SERVICE_NAME);
+    }
     if (!ChainUtil.isDict(adminConfig)) {
       return this.saveAndReturnFuncResult(context, resultPath, FunctionResultCode.FAILURE);
     }
diff --git a/db/index.js b/db/index.js
index c556f80aa..5a886007e 100644
--- a/db/index.js
+++ b/db/index.js
@@ -36,6 +36,7 @@ const {
   isValidFunctionTree,
   isValidOwnerTree,
   applyFunctionChange,
+  applyOwnerChange,
   setProofHashForStateTree,
   updateProofHashForAllRootPaths,
 } = require('./state-util');
@@ -62,19 +63,26 @@ class DB {
         GenesisAccounts, [AccountProperties.OWNER, AccountProperties.ADDRESS]);
   }
 
-  initDbStates() {
-    // Initialize DB owners.
-    this.writeDatabase([PredefinedDbPaths.OWNERS_ROOT], {
-      [OwnerProperties.OWNER]: {
-        [OwnerProperties.OWNERS]: {
-          [OwnerProperties.ANYONE]: buildOwnerPermissions(true, true, true, true),
+  initDbStates(snapshot) {
+    if (snapshot) {
+      this.writeDatabase([PredefinedDbPaths.OWNERS_ROOT], JSON.parse(JSON.stringify(snapshot[PredefinedDbPaths.OWNERS_ROOT])));
+      this.writeDatabase([PredefinedDbPaths.RULES_ROOT], JSON.parse(JSON.stringify(snapshot[PredefinedDbPaths.RULES_ROOT])));
+      this.writeDatabase([PredefinedDbPaths.VALUES_ROOT], JSON.parse(JSON.stringify(snapshot[PredefinedDbPaths.VALUES_ROOT])));
+      this.writeDatabase([PredefinedDbPaths.FUNCTIONS_ROOT], JSON.parse(JSON.stringify(snapshot[PredefinedDbPaths.FUNCTIONS_ROOT])));
+    } else {
+      // Initialize DB owners.
+      this.writeDatabase([PredefinedDbPaths.OWNERS_ROOT], {
+        [OwnerProperties.OWNER]: {
+          [OwnerProperties.OWNERS]: {
+            [OwnerProperties.ANYONE]: buildOwnerPermissions(true, true, true, true),
+          }
         }
-      }
-    });
-    // Initialize DB rules.
-    this.writeDatabase([PredefinedDbPaths.RULES_ROOT], {
-      [RuleProperties.WRITE]: true
-    });
+      });
+      // Initialize DB rules.
+      this.writeDatabase([PredefinedDbPaths.RULES_ROOT], {
+        [RuleProperties.WRITE]: true
+      });
+    }
   }
 
   /**
@@ -691,12 +699,12 @@ class DB {
     return this.setValue(valuePath, valueAfter, auth, timestamp, transaction, isGlobal);
   }
 
-  setFunction(functionPath, functionChange, auth, isGlobal) {
-    const isValidObj = isValidJsObjectForStates(functionChange);
+  setFunction(functionPath, func, auth, isGlobal) {
+    const isValidObj = isValidJsObjectForStates(func);
     if (!isValidObj.isValid) {
       return ChainUtil.returnTxResult(401, `Invalid object for states: ${isValidObj.invalidPath}`);
     }
-    const isValidFunction = isValidFunctionTree(functionChange);
+    const isValidFunction = isValidFunctionTree(func);
     if (!isValidFunction.isValid) {
       return ChainUtil.returnTxResult(405, `Invalid function tree: ${isValidFunction.invalidPath}`);
     }
@@ -706,7 +714,7 @@ class DB {
       return ChainUtil.returnTxResult(402, `Invalid path: ${isValidPath.invalidPath}`);
     }
     if (!auth || auth.addr !== this.ownerAddress) {
-      const ownerOnlyFid = this.func.hasOwnerOnlyFunction(functionChange);
+      const ownerOnlyFid = this.func.hasOwnerOnlyFunction(func);
       if (ownerOnlyFid !== null) {
         return ChainUtil.returnTxResult(
             403, `Trying to write owner-only function: ${ownerOnlyFid}`);
@@ -722,7 +730,7 @@ class DB {
       return ChainUtil.returnTxResult(404, `No write_function permission on: ${functionPath}`);
     }
     const curFunction = this.getFunction(functionPath, isGlobal);
-    const newFunction = applyFunctionChange(curFunction, functionChange);
+    const newFunction = applyFunctionChange(curFunction, func);
     const fullPath = DB.getFullPath(localPath, PredefinedDbPaths.FUNCTIONS_ROOT);
     this.writeDatabase(fullPath, newFunction);
     return ChainUtil.returnTxResult(0, null, 1);
@@ -781,9 +789,10 @@ class DB {
       return ChainUtil.returnTxResult(
           603, `No write_owner or branch_owner permission on: ${ownerPath}`);
     }
+    const curOwner = this.getOwner(ownerPath, isGlobal);
+    const newOwner = applyOwnerChange(curOwner, owner);
     const fullPath = DB.getFullPath(localPath, PredefinedDbPaths.OWNERS_ROOT);
-    const ownerCopy = ChainUtil.isDict(owner) ? JSON.parse(JSON.stringify(owner)) : owner;
-    this.writeDatabase(fullPath, ownerCopy);
+    this.writeDatabase(fullPath, newOwner);
     return ChainUtil.returnTxResult(0, null, 1);
   }
 
@@ -901,14 +910,9 @@ class DB {
       if (blockNumber > 0) {
         // Use only the service gas amount total
         result.gas_cost_total = ChainUtil.getTotalGasCost(gasPrice, gasAmountTotal.service);
-        if (result.gas_cost_total > 0) {
-          const gasFeeCollectPath = PathUtil.getGasFeeCollectPath(auth.addr, blockNumber, tx.hash);
-          const gasFeeCollectRes = this.setValue(
-              gasFeeCollectPath, { amount: result.gas_cost_total }, auth, timestamp, tx, false);
-          if (ChainUtil.isFailedTx(gasFeeCollectRes)) {
-            return ChainUtil.returnTxResult(
-                15, `Failed to collect gas fee: ${JSON.stringify(gasFeeCollectRes, null, 2)}`, 0);
-          }
+        const collectFeeRes = this.checkBillingAndCollectFee(op, auth, timestamp, tx, blockNumber, result);
+        if (collectFeeRes !== true) {
+          return collectFeeRes;
         }
       }
       if (tx && auth && auth.addr && !auth.fid) {
@@ -918,6 +922,48 @@ class DB {
     return result;
   }
 
+  checkBillingAndCollectFee(op, auth, timestamp, tx, blockNumber, result) {
+    if (result.gas_cost_total <= 0) { // No fees to collect
+      return true;
+    }
+
+    const billing = tx.tx_body.billing;
+    if (!billing) { // Charge the individual account
+      return this.collectFee(auth.addr, result.gas_cost_total, auth, timestamp, tx, blockNumber);
+    }
+    const billingParsed = billing.split('|');
+    if (billingParsed.length !== 2) {
+      const reason = 'Invalid billing param';
+      return ChainUtil.returnTxResult(15, `Failed to collect gas fee: ${reason}`, 0);
+    }
+    const billingAppName = billingParsed[0];
+    const billingServiceAcntName = ChainUtil.toBillingAccountName(billing);
+    const appNameList = ChainUtil.getServiceDependentAppNameList(op);
+    if (appNameList.length > 1) {
+      // More than 1 apps are involved. Cannot charge an app-related billing account.
+      const reason = 'Multiple app-dependent service operations for a billing account';
+      return ChainUtil.returnTxResult(16, `Failed to collect gas fee: ${reason}`, 0);
+    } else if (appNameList.length === 1 && appNameList[0] !== billingAppName) {
+      // Tx app name doesn't match the billing account.
+      const reason = 'Invalid billing account';
+      return ChainUtil.returnTxResult(17, `Failed to collect gas fee: ${reason}`, 0);
+    }
+    // Either app-independent or app name matches the billing account.
+    return this.collectFee(
+        billingServiceAcntName, result.gas_cost_total, auth, timestamp, tx, blockNumber);
+  }
+
+  collectFee(billedTo, gasCost, auth, timestamp, tx, blockNumber) {
+    const gasFeeCollectPath = PathUtil.getGasFeeCollectPath(billedTo, blockNumber, tx.hash);
+    const gasFeeCollectRes = this.setValue(
+        gasFeeCollectPath, { amount: gasCost }, auth, timestamp, tx, false);
+    if (ChainUtil.isFailedTx(gasFeeCollectRes)) {
+      return ChainUtil.returnTxResult(
+          18, `Failed to collect gas fee: ${JSON.stringify(gasFeeCollectRes, null, 2)}`, 0);
+    }
+    return true;
+  }
+
   executeTransaction(tx, blockNumber = 0) {
     const LOG_HEADER = 'executeTransaction';
     // NOTE(platfowner): A transaction needs to be converted to an executable form
diff --git a/db/rule-util.js b/db/rule-util.js
index 1314f45ec..86eeedfc5 100644
--- a/db/rule-util.js
+++ b/db/rule-util.js
@@ -67,11 +67,22 @@ class RuleUtil {
   }
 
   isServAcntName(name) {
-    return this.isString(name) && name.split('|').length >= 3;
+    const { isServiceAccountServiceType } = require('../common/constants');
+    const { isValidServiceName } = require('./state-util');
+
+    if (!this.isString(name)) {
+      return false;
+    }
+    const parsed = name.split('|');
+    if (parsed.length < 3) {
+      return false;
+    }
+    return isServiceAccountServiceType(parsed[0]) && isValidServiceName(parsed[1]);
   }
 
   isValShardProto(value) {
-    const {ShardingProtocols} = require('../common/constants');
+    const { ShardingProtocols } = require('../common/constants');
+
     return value === ShardingProtocols.NONE || value === ShardingProtocols.POA;
   }
 
@@ -135,6 +146,16 @@ class RuleUtil {
     }
   }
 
+  getBillingUserPath(billingServAcntName, userAddr) {
+    const { PredefinedDbPaths } = require('../common/constants');
+    const parsed = this.parseServAcntName(billingServAcntName);
+    const appName = parsed[1];
+    const billingId = parsed[2];
+    return `/${PredefinedDbPaths.MANAGE_APP}/${appName}/${PredefinedDbPaths.MANAGE_APP_CONFIG}/` +
+        `${PredefinedDbPaths.MANAGE_APP_CONFIG_BILLING}/${billingId}/` +
+        `${PredefinedDbPaths.MANAGE_APP_CONFIG_BILLING_USERS}/${userAddr}`;
+  }
+
   getOwnerAddr() {
     const { GenesisAccounts, AccountProperties } = require('../common/constants');
     return _.get(GenesisAccounts, `${AccountProperties.OWNER}.${AccountProperties.ADDRESS}`, null);
diff --git a/db/state-util.js b/db/state-util.js
index f9bc16181..13d18728c 100644
--- a/db/state-util.js
+++ b/db/state-util.js
@@ -7,9 +7,11 @@ const ChainUtil = require('../common/chain-util');
 const {
   FunctionProperties,
   FunctionTypes,
+  isNativeFunctionId,
   RuleProperties,
   OwnerProperties,
   ShardingProperties,
+  STATE_LABEL_LENGTH_LIMIT,
 } = require('../common/constants');
 const Functions = require('./functions');
 
@@ -88,6 +90,11 @@ function isWritablePathWithSharding(fullPath, root) {
   return {isValid, invalidPath: isValid ? '' : ChainUtil.formatPath(path)};
 }
 
+function hasVarNamePattern(name) {
+  const varNameRegex = /^[A-Za-z_]+[A-Za-z0-9_]*$/gm;
+  return ChainUtil.isString(name) ? varNameRegex.test(name) : false;
+}
+
 function hasReservedChar(label) {
   const reservedCharRegex = /[\/\.\$\*#\{\}\[\]<>'"` \x00-\x1F\x7F]/gm;
   return ChainUtil.isString(label) ? reservedCharRegex.test(label) : false;
@@ -100,9 +107,14 @@ function hasAllowedPattern(label) {
       (wildCardPatternRegex.test(label) || configPatternRegex.test(label)) : false;
 }
 
+function isValidServiceName(name) {
+  return hasVarNamePattern(name);
+}
+
 function isValidStateLabel(label) {
   if (!ChainUtil.isString(label) ||
       label === '' ||
+      label.length > STATE_LABEL_LENGTH_LIMIT ||
       (hasReservedChar(label) && !hasAllowedPattern(label))) {
     return false;
   }
@@ -156,51 +168,51 @@ function isValidJsObjectForStates(obj) {
 /**
  * Checks the validity of the given rule configuration.
  */
- function isValidRuleConfig(ruleConfig) {
-  if (!ChainUtil.isBool(ruleConfig) && !ChainUtil.isString(ruleConfig)) {
+ function isValidRuleConfig(ruleConfigObj) {
+  if (!ChainUtil.isBool(ruleConfigObj) && !ChainUtil.isString(ruleConfigObj)) {
     return { isValid: false, invalidPath: ChainUtil.formatPath([]) };
   }
 
   return { isValid: true, invalidPath: '' };
 }
 
-function sanitizeFunctionInfo(functionInfo) {
-  if (!functionInfo) {
+function sanitizeFunctionInfo(functionInfoObj) {
+  if (!functionInfoObj) {
     return null;
   }
 
-  const functionType = functionInfo[FunctionProperties.FUNCTION_TYPE];
+  const functionType = functionInfoObj[FunctionProperties.FUNCTION_TYPE];
   const sanitized = {};
   if (functionType === FunctionTypes.NATIVE) {
     sanitized[FunctionProperties.FUNCTION_TYPE] = functionType;
     sanitized[FunctionProperties.FUNCTION_ID] =
-        ChainUtil.stringOrEmpty(functionInfo[FunctionProperties.FUNCTION_ID]);
+        ChainUtil.stringOrEmpty(functionInfoObj[FunctionProperties.FUNCTION_ID]);
   } else if (functionType === FunctionTypes.REST) {
     sanitized[FunctionProperties.FUNCTION_TYPE] = functionType;
     sanitized[FunctionProperties.FUNCTION_ID] =
-        ChainUtil.stringOrEmpty(functionInfo[FunctionProperties.FUNCTION_ID]);
+        ChainUtil.stringOrEmpty(functionInfoObj[FunctionProperties.FUNCTION_ID]);
     sanitized[FunctionProperties.EVENT_LISTENER] =
-        ChainUtil.stringOrEmpty(functionInfo[FunctionProperties.EVENT_LISTENER]);
+        ChainUtil.stringOrEmpty(functionInfoObj[FunctionProperties.EVENT_LISTENER]);
     sanitized[FunctionProperties.SERVICE_NAME] =
-        ChainUtil.stringOrEmpty(functionInfo[FunctionProperties.SERVICE_NAME]);
+        ChainUtil.stringOrEmpty(functionInfoObj[FunctionProperties.SERVICE_NAME]);
   }
 
   return sanitized;
 }
 
-function isValidFunctionInfo(functionInfo) {
-  if (ChainUtil.isEmpty(functionInfo)) {
+function isValidFunctionInfo(functionInfoObj) {
+  if (ChainUtil.isEmpty(functionInfoObj)) {
     return false;
   }
-  const sanitized = sanitizeFunctionInfo(functionInfo);
+  const sanitized = sanitizeFunctionInfo(functionInfoObj);
   const isIdentical =
-      _.isEqual(JSON.parse(JSON.stringify(sanitized)), functionInfo, { strict: true });
+      _.isEqual(JSON.parse(JSON.stringify(sanitized)), functionInfoObj, { strict: true });
   if (!isIdentical) {
     return false;
   }
-  const eventListener = functionInfo[FunctionProperties.EVENT_LISTENER];
+  const eventListener = functionInfoObj[FunctionProperties.EVENT_LISTENER];
   if (eventListener !== undefined &&
-      !validUrl.isUri(functionInfo[FunctionProperties.EVENT_LISTENER])) {
+      !validUrl.isUri(functionInfoObj[FunctionProperties.EVENT_LISTENER])) {
     return false;
   }
   return true;
@@ -209,17 +221,17 @@ function isValidFunctionInfo(functionInfo) {
 /**
  * Checks the validity of the given function configuration.
  */
-function isValidFunctionConfig(functionConfig) {
-  if (!ChainUtil.isDict(functionConfig)) {
+function isValidFunctionConfig(functionConfigObj) {
+  if (!ChainUtil.isDict(functionConfigObj)) {
     return { isValid: false, invalidPath: ChainUtil.formatPath([]) };
   }
-  const fidList = Object.keys(functionConfig);
+  const fidList = Object.keys(functionConfigObj);
   if (ChainUtil.isEmpty(fidList)) {
     return { isValid: false, invalidPath: ChainUtil.formatPath([]) };
   }
   for (const fid of fidList) {
     const invalidPath = ChainUtil.formatPath([fid]);
-    const functionInfo = functionConfig[fid];
+    const functionInfo = functionConfigObj[fid];
     if (functionInfo === null) {
       // Function deletion.
       continue;
@@ -238,41 +250,41 @@ function isValidFunctionConfig(functionConfig) {
   return { isValid: true, invalidPath: '' };
 }
 
-function sanitizeOwnerPermissions(ownerPermissions) {
-  if (!ownerPermissions) {
+function sanitizeOwnerPermissions(ownerPermissionsObj) {
+  if (!ownerPermissionsObj) {
     return null;
   }
   return {
     [OwnerProperties.BRANCH_OWNER]:
-        ChainUtil.boolOrFalse(ownerPermissions[OwnerProperties.BRANCH_OWNER]),
+        ChainUtil.boolOrFalse(ownerPermissionsObj[OwnerProperties.BRANCH_OWNER]),
     [OwnerProperties.WRITE_FUNCTION]:
-        ChainUtil.boolOrFalse(ownerPermissions[OwnerProperties.WRITE_FUNCTION]),
+        ChainUtil.boolOrFalse(ownerPermissionsObj[OwnerProperties.WRITE_FUNCTION]),
     [OwnerProperties.WRITE_OWNER]:
-        ChainUtil.boolOrFalse(ownerPermissions[OwnerProperties.WRITE_OWNER]),
+        ChainUtil.boolOrFalse(ownerPermissionsObj[OwnerProperties.WRITE_OWNER]),
     [OwnerProperties.WRITE_RULE]:
-        ChainUtil.boolOrFalse(ownerPermissions[OwnerProperties.WRITE_RULE]),
+        ChainUtil.boolOrFalse(ownerPermissionsObj[OwnerProperties.WRITE_RULE]),
   };
 }
 
-function isValidOwnerPermissions(ownerPermissions) {
-  if (ChainUtil.isEmpty(ownerPermissions)) {
+function isValidOwnerPermissions(ownerPermissionsObj) {
+  if (ChainUtil.isEmpty(ownerPermissionsObj)) {
     return false;
   }
-  const sanitized = sanitizeOwnerPermissions(ownerPermissions);
+  const sanitized = sanitizeOwnerPermissions(ownerPermissionsObj);
   const isIdentical =
-      _.isEqual(JSON.parse(JSON.stringify(sanitized)), ownerPermissions, { strict: true });
+      _.isEqual(JSON.parse(JSON.stringify(sanitized)), ownerPermissionsObj, { strict: true });
   return isIdentical;
 }
 
 /**
  * Checks the validity of the given owner configuration.
  */
-function isValidOwnerConfig(ownerConfig) {
-  if (!ChainUtil.isDict(ownerConfig)) {
+function isValidOwnerConfig(ownerConfigObj) {
+  if (!ChainUtil.isDict(ownerConfigObj)) {
     return { isValid: false, invalidPath: ChainUtil.formatPath([]) };
   }
   const path = [];
-  const ownersProp = ownerConfig[OwnerProperties.OWNERS];
+  const ownersProp = ownerConfigObj[OwnerProperties.OWNERS];
   if (ownersProp === undefined) {
     return { isValid: false, invalidPath: ChainUtil.formatPath(path) };
   }
@@ -291,11 +303,15 @@ function isValidOwnerConfig(ownerConfig) {
         return { isValid: false, invalidPath };
       }
       const fid = owner.substring(OwnerProperties.FID_PREFIX.length);
-      if (!Functions.isNativeFunctionId(fid)) {
+      if (!isNativeFunctionId(fid)) {
         return { isValid: false, invalidPath };
       }
     }
-    const ownerPermissions = ChainUtil.getJsObject(ownerConfig, [...path, owner]);
+    const ownerPermissions = ChainUtil.getJsObject(ownerConfigObj, [...path, owner]);
+    if (ownerPermissions === null) {
+      // Owner deletion.
+      continue;
+    }
     if (!isValidOwnerPermissions(ownerPermissions)) {
       return { isValid: false, invalidPath };
     }
@@ -304,14 +320,14 @@ function isValidOwnerConfig(ownerConfig) {
   return { isValid: true, invalidPath: '' };
 }
 
-function isValidConfigTreeRecursive(stateTree, path, configLabel, stateConfigValidator) {
-  if (!ChainUtil.isDict(stateTree) || ChainUtil.isEmpty(stateTree)) {
+function isValidConfigTreeRecursive(stateTreeObj, path, configLabel, stateConfigValidator) {
+  if (!ChainUtil.isDict(stateTreeObj) || ChainUtil.isEmpty(stateTreeObj)) {
     return { isValid: false, invalidPath: ChainUtil.formatPath(path) };
   }
 
-  for (const label in stateTree) {
+  for (const label in stateTreeObj) {
     path.push(label);
-    const subtree = stateTree[label];
+    const subtree = stateTreeObj[label];
     if (label === configLabel) {
       const isValidConfig = stateConfigValidator(subtree);
       if (!isValidConfig.isValid) {
@@ -336,73 +352,140 @@ function isValidConfigTreeRecursive(stateTree, path, configLabel, stateConfigVal
 /**
  * Checks the validity of the given rule tree.
  */
-function isValidRuleTree(ruleTree) {
-  if (ruleTree === null) {
+function isValidRuleTree(ruleTreeObj) {
+  if (ruleTreeObj === null) {
     return { isValid: true, invalidPath: '' };
   }
 
-  return isValidConfigTreeRecursive(ruleTree, [], RuleProperties.WRITE, isValidRuleConfig);
+  return isValidConfigTreeRecursive(ruleTreeObj, [], RuleProperties.WRITE, isValidRuleConfig);
 }
 
 /**
  * Checks the validity of the given function tree.
  */
-function isValidFunctionTree(functionTree) {
-  if (functionTree === null) {
+function isValidFunctionTree(functionTreeObj) {
+  if (functionTreeObj === null) {
     return { isValid: true, invalidPath: '' };
   }
 
   return isValidConfigTreeRecursive(
-      functionTree, [], FunctionProperties.FUNCTION, isValidFunctionConfig);
+      functionTreeObj, [], FunctionProperties.FUNCTION, isValidFunctionConfig);
 }
 
 /**
  * Checks the validity of the given owner tree.
  */
-function isValidOwnerTree(ownerTree) {
-  if (ownerTree === null) {
+function isValidOwnerTree(ownerTreeObj) {
+  if (ownerTreeObj === null) {
     return { isValid: true, invalidPath: '' };
   }
 
-  return isValidConfigTreeRecursive(ownerTree, [], OwnerProperties.OWNER, isValidOwnerConfig);
+  return isValidConfigTreeRecursive(ownerTreeObj, [], OwnerProperties.OWNER, isValidOwnerConfig);
 }
 
 /**
- * Returns a new function created by applying the function change to the current function.
- * @param {Object} curFunction current function (to be modified and returned by this function)
- * @param {Object} functionChange function change
+ * Returns whether the given state tree object has the given config label as a property.
  */
-function applyFunctionChange(curFunction, functionChange) {
-  if (curFunction === null) {
-    // Just write the function change.
-    return functionChange;
+function hasConfigLabel(stateTreeObj, configLabel) {
+  if (!ChainUtil.isDict(stateTreeObj)) {
+    return false;
   }
-  if (functionChange === null) {
-    // Just delete the existing value.
-    return null;
+  if (ChainUtil.getJsObject(stateTreeObj, [configLabel]) === null) {
+    return false;
+  }
+
+  return true;
+}
+
+/**
+ * Returns whether the given state tree object has the given config label as the only property.
+ */
+function hasConfigLabelOnly(stateTreeObj, configLabel) {
+  if (!hasConfigLabel(stateTreeObj, configLabel)) {
+    return false;
+  }
+  if (Object.keys(stateTreeObj).length !== 1) {
+    return false;
+  }
+
+  return true;
+}
+
+/**
+ * Returns a new function tree created by applying the function change to
+ * the current function tree.
+ * @param {Object} curFuncTree current function tree (to be modified by this function)
+ * @param {Object} functionChange function change
+ */
+function applyFunctionChange(curFuncTree, functionChange) {
+  // NOTE(platfowner): Partial set is applied only when the current function tree has
+  // .function property and the function change has .function property as the only property.
+  if (!hasConfigLabel(curFuncTree, FunctionProperties.FUNCTION) ||
+      !hasConfigLabelOnly(functionChange, FunctionProperties.FUNCTION)) {
+    return ChainUtil.isDict(functionChange) ?
+        JSON.parse(JSON.stringify(functionChange)) : functionChange;
   }
   const funcChangeMap = ChainUtil.getJsObject(functionChange, [FunctionProperties.FUNCTION]);
   if (!funcChangeMap || Object.keys(funcChangeMap).length === 0) {
-    return curFunction;
+    return curFuncTree;
   }
-  const newFunction =
-      ChainUtil.isDict(curFunction) ? JSON.parse(JSON.stringify(curFunction)) : {};
-  let newFuncMap = ChainUtil.getJsObject(newFunction, [FunctionProperties.FUNCTION]);
-  if (!newFuncMap || !ChainUtil.isDict(newFunction)) {
+  const newFuncConfig =
+      ChainUtil.isDict(curFuncTree) ? JSON.parse(JSON.stringify(curFuncTree)) : {};
+  let newFuncMap = ChainUtil.getJsObject(newFuncConfig, [FunctionProperties.FUNCTION]);
+  if (!ChainUtil.isDict(newFuncMap)) {
     // Add a place holder.
-    ChainUtil.setJsObject(newFunction, [FunctionProperties.FUNCTION], {});
-    newFuncMap = ChainUtil.getJsObject(newFunction, [FunctionProperties.FUNCTION]);
+    ChainUtil.setJsObject(newFuncConfig, [FunctionProperties.FUNCTION], {});
+    newFuncMap = ChainUtil.getJsObject(newFuncConfig, [FunctionProperties.FUNCTION]);
   }
   for (const functionKey in funcChangeMap) {
-    const functionValue = funcChangeMap[functionKey];
-    if (functionValue === null) {
+    const functionInfo = funcChangeMap[functionKey];
+    if (functionInfo === null) {
       delete newFuncMap[functionKey];
     } else {
-      newFuncMap[functionKey] = functionValue;
+      newFuncMap[functionKey] = functionInfo;
+    }
+  }
+
+  return newFuncConfig;
+}
+
+/**
+ * Returns a new owner tree created by applying the owner change to
+ * the current owner tree.
+ * @param {Object} curOwnerTree current owner tree (to be modified by this function)
+ * @param {Object} ownerChange owner change
+ */
+function applyOwnerChange(curOwnerTree, ownerChange) {
+  // NOTE(platfowner): Partial set is applied only when the current owner tree has
+  // .owner property and the owner change has .owner property as the only property.
+  if (!hasConfigLabel(curOwnerTree, OwnerProperties.OWNER) ||
+      !hasConfigLabelOnly(ownerChange, OwnerProperties.OWNER)) {
+    return ChainUtil.isDict(ownerChange) ?
+        JSON.parse(JSON.stringify(ownerChange)) : ownerChange;
+  }
+  const ownerMapPath = [OwnerProperties.OWNER, OwnerProperties.OWNERS];
+  const ownerChangeMap = ChainUtil.getJsObject(ownerChange, ownerMapPath);
+  if (!ownerChangeMap || Object.keys(ownerChangeMap).length === 0) {
+    return curOwnerTree;
+  }
+  const newOwnerConfig =
+      ChainUtil.isDict(curOwnerTree) ? JSON.parse(JSON.stringify(curOwnerTree)) : {};
+  let newOwnerMap = ChainUtil.getJsObject(newOwnerConfig, ownerMapPath);
+  if (!ChainUtil.isDict(newOwnerMap)) {
+    // Add a place holder.
+    ChainUtil.setJsObject(newOwnerConfig, ownerMapPath, {});
+    newOwnerMap = ChainUtil.getJsObject(newOwnerConfig, ownerMapPath);
+  }
+  for (const ownerKey in ownerChangeMap) {
+    const ownerPermissions = ownerChangeMap[ownerKey];
+    if (ownerPermissions === null) {
+      delete newOwnerMap[ownerKey];
+    } else {
+      newOwnerMap[ownerKey] = ownerPermissions;
     }
   }
 
-  return newFunction;
+  return newOwnerConfig;
 }
 
 /**
@@ -592,6 +675,7 @@ module.exports = {
   hasReservedChar,
   hasAllowedPattern,
   isWritablePathWithSharding,
+  isValidServiceName,
   isValidStateLabel,
   isValidPathForStates,
   isValidJsObjectForStates,
@@ -602,6 +686,7 @@ module.exports = {
   isValidOwnerConfig,
   isValidOwnerTree,
   applyFunctionChange,
+  applyOwnerChange,
   setStateTreeVersion,
   renameStateTreeVersion,
   deleteStateTree,
diff --git a/genesis-configs/afan-shard/genesis_params.json b/genesis-configs/afan-shard/genesis_params.json
index 8316ca6b0..c7e4124ba 100644
--- a/genesis-configs/afan-shard/genesis_params.json
+++ b/genesis-configs/afan-shard/genesis_params.json
@@ -27,12 +27,13 @@
     "DEFAULT_MAX_INBOUND": 3
   },
   "resource": {
-    "BATCH_TX_LIST_SIZE_LIMIT": 50,
     "TREE_HEIGHT_LIMIT": 20,
     "TREE_SIZE_LIMIT": 1000000,
+    "STATE_LABEL_LENGTH_LIMIT": 150,
     "TX_BYTES_LIMIT": 10000,
+    "BATCH_TX_LIST_SIZE_LIMIT": 50,
     "TX_POOL_SIZE_LIMIT": 1000,
-    "TX_POOL_SIZE_LIMIT_PER_ACCOUNT": 50,
+    "TX_POOL_SIZE_LIMIT_PER_ACCOUNT": 100,
     "BANDWIDTH_BUDGET_PER_BLOCK": 10000,
     "SERVICE_BANDWIDTH_BUDGET_PER_BLOCK": 5000
   }
diff --git a/genesis-configs/base/genesis_params.json b/genesis-configs/base/genesis_params.json
index e8a74899a..5e586f926 100644
--- a/genesis-configs/base/genesis_params.json
+++ b/genesis-configs/base/genesis_params.json
@@ -27,12 +27,13 @@
     "DEFAULT_MAX_INBOUND": 3
   },
   "resource": {
-    "BATCH_TX_LIST_SIZE_LIMIT": 50,
     "TREE_HEIGHT_LIMIT": 20,
     "TREE_SIZE_LIMIT": 1000000,
+    "STATE_LABEL_LENGTH_LIMIT": 150,
     "TX_BYTES_LIMIT": 10000,
+    "BATCH_TX_LIST_SIZE_LIMIT": 50,
     "TX_POOL_SIZE_LIMIT": 1000,
-    "TX_POOL_SIZE_LIMIT_PER_ACCOUNT": 50,
+    "TX_POOL_SIZE_LIMIT_PER_ACCOUNT": 100,
     "BANDWIDTH_BUDGET_PER_BLOCK": 10000,
     "SERVICE_BANDWIDTH_BUDGET_PER_BLOCK": 5000
   }
diff --git a/genesis-configs/base/genesis_rules.json b/genesis-configs/base/genesis_rules.json
index a123330eb..c6d9a0387 100644
--- a/genesis-configs/base/genesis_rules.json
+++ b/genesis-configs/base/genesis_rules.json
@@ -75,7 +75,7 @@
       "$from": {
         "$block_number": {
           "$tx_hash": {
-            ".write": "auth.addr === $from && data === null && util.isDict(newData) && util.isNumber(newData.amount) && newData.amount <= getValue(util.getBalancePath($from))"
+            ".write": "(auth.addr === $from || (util.isServAcntName($from) && getValue(util.getBillingUserPath($from, auth.addr)) === true)) && data === null && util.isDict(newData) && util.isNumber(newData.amount) && newData.amount <= getValue(util.getBalancePath($from))"
           }
         }
       }
diff --git a/genesis-configs/sim-shard/genesis_params.json b/genesis-configs/sim-shard/genesis_params.json
index 864496abf..d01e0c7ab 100644
--- a/genesis-configs/sim-shard/genesis_params.json
+++ b/genesis-configs/sim-shard/genesis_params.json
@@ -27,12 +27,13 @@
     "DEFAULT_MAX_INBOUND": 3
   },
   "resource": {
-    "BATCH_TX_LIST_SIZE_LIMIT": 50,
     "TREE_HEIGHT_LIMIT": 20,
     "TREE_SIZE_LIMIT": 1000000,
+    "STATE_LABEL_LENGTH_LIMIT": 150,
     "TX_BYTES_LIMIT": 10000,
+    "BATCH_TX_LIST_SIZE_LIMIT": 50,
     "TX_POOL_SIZE_LIMIT": 1000,
-    "TX_POOL_SIZE_LIMIT_PER_ACCOUNT": 50,
+    "TX_POOL_SIZE_LIMIT_PER_ACCOUNT": 100,
     "BANDWIDTH_BUDGET_PER_BLOCK": 10000,
     "SERVICE_BANDWIDTH_BUDGET_PER_BLOCK": 5000
   }
diff --git a/genesis-configs/testnet/genesis_params.json b/genesis-configs/testnet/genesis_params.json
index 67c118cfa..3ebd89a5e 100644
--- a/genesis-configs/testnet/genesis_params.json
+++ b/genesis-configs/testnet/genesis_params.json
@@ -27,12 +27,13 @@
     "DEFAULT_MAX_INBOUND": 3
   },
   "resource": {
-    "BATCH_TX_LIST_SIZE_LIMIT": 50,
     "TREE_HEIGHT_LIMIT": 20,
     "TREE_SIZE_LIMIT": 1000000,
+    "STATE_LABEL_LENGTH_LIMIT": 150,
     "TX_BYTES_LIMIT": 10000,
+    "BATCH_TX_LIST_SIZE_LIMIT": 50,
     "TX_POOL_SIZE_LIMIT": 1000,
-    "TX_POOL_SIZE_LIMIT_PER_ACCOUNT": 50,
+    "TX_POOL_SIZE_LIMIT_PER_ACCOUNT": 100,
     "BANDWIDTH_BUDGET_PER_BLOCK": 10000,
     "SERVICE_BANDWIDTH_BUDGET_PER_BLOCK": 5000
   }
diff --git a/integration/node.test.js b/integration/node.test.js
index 63d3cc806..0be27b9b5 100644
--- a/integration/node.test.js
+++ b/integration/node.test.js
@@ -20,6 +20,7 @@ const {
   TX_BYTES_LIMIT,
   BATCH_TX_LIST_SIZE_LIMIT,
   TX_POOL_SIZE_LIMIT_PER_ACCOUNT,
+  MICRO_AIN,
 } = require('../common/constants');
 const ChainUtil = require('../common/chain-util');
 const { waitUntilTxFinalized, parseOrLog } = require('../unittest/test-util');
@@ -2537,41 +2538,47 @@ describe('Blockchain Node', () => {
           nonce: -1
         };
 
-        const txList1 = [];
         // Not over the limit.
-        for (let i = 0; i < TX_POOL_SIZE_LIMIT_PER_ACCOUNT; i++) {
-          const txBody = JSON.parse(JSON.stringify(txBodyTemplate));
-          txBody.timestamp = timestamp + i;
-          const signature =
-              ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex'));
-          txList1.push({
-            tx_body: txBody,
-            signature,
+        let txCount = 0;
+        while (txCount < TX_POOL_SIZE_LIMIT_PER_ACCOUNT) {
+          const remainingTxCount = TX_POOL_SIZE_LIMIT_PER_ACCOUNT - txCount;
+          const batchTxSize = (remainingTxCount >= BATCH_TX_LIST_SIZE_LIMIT) ?
+              BATCH_TX_LIST_SIZE_LIMIT : remainingTxCount;
+          const txList1 = [];
+          for (let i = 0; i < batchTxSize; i++) {
+            const txBody = JSON.parse(JSON.stringify(txBodyTemplate));
+            txBody.timestamp = timestamp + txCount + i;
+            const signature =
+                ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex'));
+            txList1.push({
+              tx_body: txBody,
+              signature,
+            });
+          }
+          const res1 = await client.request('ain_sendSignedTransactionBatch', {
+            tx_list: txList1,
+            protoVer: CURRENT_PROTOCOL_VERSION
           });
-        }
-        const res1 = await client.request('ain_sendSignedTransactionBatch', {
-          tx_list: txList1,
-          protoVer: CURRENT_PROTOCOL_VERSION
-        });
-        const resultList1 = _.get(res1, 'result.result', null);
-        // Accepts transactions.
-        expect(ChainUtil.isArray(resultList1)).to.equal(true);
-        for (let i = 0; i < resultList1.length; i++) {
-          expect(ChainUtil.isFailedTx(resultList1[i].result)).to.equal(false);
+          const resultList1 = _.get(res1, 'result.result', null);
+          // Accepts transactions.
+          expect(ChainUtil.isArray(resultList1)).to.equal(true);
+          for (let i = 0; i < resultList1.length; i++) {
+            expect(ChainUtil.isFailedTx(resultList1[i].result)).to.equal(false);
+          }
+
+          txCount += batchTxSize;
         }
 
-        const txList2 = [];
         // Just over the limit.
-        for (let i = 0; i < 1; i++) {
-          const txBody = JSON.parse(JSON.stringify(txBodyTemplate));
-          txBody.timestamp = timestamp + TX_POOL_SIZE_LIMIT_PER_ACCOUNT + i;
-          const signature =
-              ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex'));
-          txList2.push({
-            tx_body: txBody,
-            signature,
-          });
-        }
+        const txList2 = [];
+        const txBody = JSON.parse(JSON.stringify(txBodyTemplate));
+        txBody.timestamp = timestamp + TX_POOL_SIZE_LIMIT_PER_ACCOUNT + 1;
+        const signature =
+            ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex'));
+        txList2.push({
+          tx_body: txBody,
+          signature,
+        });
         const res2 = await client.request('ain_sendSignedTransactionBatch', {
           tx_list: txList2,
           protoVer: CURRENT_PROTOCOL_VERSION
@@ -2585,7 +2592,7 @@ describe('Blockchain Node', () => {
           {
             "result": {
               "code": 4,
-              "error_message": "[executeTransactionAndAddToPool] Tx pool does NOT have enough room (50) for account: 0x85a620A5A46d01cc1fCF49E73ab00710d4da943E",
+              "error_message": "[executeTransactionAndAddToPool] Tx pool does NOT have enough room (100) for account: 0x85a620A5A46d01cc1fCF49E73ab00710d4da943E",
               "gas_amount": 0
             },
             "tx_hash": "erased"
@@ -3713,9 +3720,125 @@ describe('Blockchain Node', () => {
       });
     });
 
+    describe('App creation', () => {
+      before(async () => {
+        const appStakingPath =
+            `/staking/test_service_create_app/${serviceAdmin}/0/stake/${Date.now()}/value`;
+        const appStakingRes = parseOrLog(syncRequest('POST', server1 + '/set_value', {json: {
+          ref: appStakingPath,
+          value: 1
+        }}).body.toString('utf-8')).result;
+        if (!(await waitUntilTxFinalized(serverList, appStakingRes.tx_hash))) {
+          console.error(`Failed to check finalization of tx.`);
+        }
+      });
+
+      it("when successful with valid app name", async () => {
+        const manageAppPath = '/manage_app/test_service_create_app0/create/1';
+        const createAppRes = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: {
+          ref: manageAppPath,
+          value: {
+            admin: { [serviceAdmin]: true },
+          },
+          nonce: -1,
+          timestamp: 1234567890000,
+        }}).body.toString('utf-8')).result;
+        assert.deepEqual(createAppRes, {
+          "result": {
+            "code": 0,
+            "func_results": {
+              "_createApp": {
+                "code": "SUCCESS",
+                "gas_amount": 0,
+                "op_results": [
+                  {
+                    "path": "/apps/test_service_create_app0",
+                    "result": {
+                      "code": 0,
+                      "gas_amount": 1
+                    }
+                  },
+                  {
+                    "path": "/apps/test_service_create_app0",
+                    "result": {
+                      "code": 0,
+                      "gas_amount": 1
+                    }
+                  },
+                  {
+                    "path": "/manage_app/test_service_create_app0/config",
+                    "result": {
+                      "code": 0,
+                      "gas_amount": 1
+                    }
+                  },
+                  {
+                    "path": "/manage_app/test_service_create_app0/create/1/result",
+                    "result": {
+                      "code": 0,
+                      "gas_amount": 1
+                    }
+                  }
+                ]
+              }
+            },
+            "gas_amount": 1,
+            "gas_amount_total": {
+              "app": {
+                "test_service_create_app0": 2
+              },
+              "service": 3
+            },
+            "gas_cost_total": 0
+          },
+          "tx_hash": "0x4e2a4bc009347bbaa1a14f1ddecb0f2b06d02d46326d33def7c346c613093079"
+        });
+      });
+
+      it("when failed with invalid app name", async () => {
+        const manageAppPath = '/manage_app/0test_service_create_app/create/1';
+        const createAppRes = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: {
+          ref: manageAppPath,
+          value: {
+            admin: { [serviceAdmin]: true },
+          },
+          nonce: -1,
+          timestamp: 1234567890000,
+        }}).body.toString('utf-8')).result;
+        assert.deepEqual(createAppRes, {
+          "result": {
+            "code": 0,
+            "func_results": {
+              "_createApp": {
+                "code": "INVALID_SERVICE_NAME",
+                "gas_amount": 0,
+                "op_results": [
+                  {
+                    "path": "/manage_app/0test_service_create_app/create/1/result",
+                    "result": {
+                      "code": 0,
+                      "gas_amount": 1
+                    }
+                  }
+                ]
+              }
+            },
+            "gas_amount": 1,
+            "gas_amount_total": {
+              "app": {},
+              "service": 2
+            },
+            "gas_cost_total": 0
+          },
+          "tx_hash": "0x60f6a71fedc8bbe457680ff6cf2e24b5c2097718f226c4f40fb4f9849d52f7fa"
+        });
+      });
+    });
+
     describe('Gas fee', () => {
       before(async () => {
-        const appStakingPath = `/staking/test_service_gas_fee/${serviceAdmin}/0/stake/${Date.now()}/value`
+        const appStakingPath =
+            `/staking/test_service_gas_fee/${serviceAdmin}/0/stake/${Date.now()}/value`;
         const appStakingRes = parseOrLog(syncRequest('POST', server1 + '/set_value', {json: {
           ref: appStakingPath,
           value: 1
@@ -3723,7 +3846,7 @@ describe('Blockchain Node', () => {
         if (!(await waitUntilTxFinalized(serverList, appStakingRes.tx_hash))) {
           console.error(`Failed to check finalization of tx.`);
         }
-        const manageAppPath = '/manage_app/test_service_gas_fee/create/1'
+        const manageAppPath = '/manage_app/test_service_gas_fee/create/1';
         const createAppRes = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: {
           ref: manageAppPath,
           value: {
@@ -4136,6 +4259,113 @@ describe('Blockchain Node', () => {
         }}).body.toString('utf-8'));
         expect(bodyToUpperCase.code).to.equals(1);
       });
+
+      it('transfer: transfer with valid service account service type', async () => {
+        let fromBeforeBalance = parseOrLog(syncRequest('GET',
+            server2 + `/get_value?ref=${transferFromBalancePath}`).body.toString('utf-8')).result;
+        const transferToService = `staking|test_service|${transferTo}|0`;
+        const transferToServiceBalancePath =
+            `/service_accounts/staking/test_service/${transferTo}|0/balance`;
+        const toServiceBeforeBalance = parseOrLog(syncRequest('GET',
+            server2 + `/get_value?ref=${transferToServiceBalancePath}`)
+            .body.toString('utf-8')).result || 0;
+        const transferServicePath = `/transfer/${transferFrom}/${transferToService}`;
+        const body = parseOrLog(syncRequest('POST', server1 + '/set_value', {json: {
+          ref: transferServicePath + '/1/value',
+          value: transferAmount,
+          nonce: -1,
+          timestamp: 1234567890000,
+        }}).body.toString('utf-8'));
+        assert.deepEqual(body, {
+          "code": 0,
+          "result": {
+            "result": {
+              "code": 0,
+              "func_results": {
+                "_transfer": {
+                  "code": "SUCCESS",
+                  "gas_amount": 1000,
+                  "op_results": [
+                    {
+                      "path": "/accounts/0x00ADEc28B6a845a085e03591bE7550dd68673C1C/balance",
+                      "result": {
+                        "code": 0,
+                        "gas_amount": 1
+                      }
+                    },
+                    {
+                      "path": "/service_accounts/staking/test_service/0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204|0/balance",
+                      "result": {
+                        "code": 0,
+                        "gas_amount": 1
+                      }
+                    },
+                    {
+                      "path": "/transfer/0x00ADEc28B6a845a085e03591bE7550dd68673C1C/staking|test_service|0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204|0/1/result",
+                      "result": {
+                        "code": 0,
+                        "gas_amount": 1
+                      }
+                    }
+                  ]
+                }
+              },
+              "gas_amount": 1,
+              "gas_amount_total": {
+                "app": {},
+                "service": 1004
+              },
+              "gas_cost_total": 0
+            },
+            "tx_hash": "0x62f01969d903d7a6f184279634249941a2c312e896f045c071afe78ac635fe96"
+          }
+        });
+        if (!(await waitUntilTxFinalized([server2], _.get(body, 'result.tx_hash')))) {
+          console.error(`Failed to check finalization of tx.`);
+        }
+        const fromAfterBalance = parseOrLog(syncRequest('GET',
+            server2 + `/get_value?ref=${transferFromBalancePath}`).body.toString('utf-8')).result;
+        const toServiceAfterBalance = parseOrLog(syncRequest('GET',
+            server2 + `/get_value?ref=${transferToServiceBalancePath}`).body.toString('utf-8')).result;
+        const resultCode = parseOrLog(syncRequest('GET',
+            server2 + `/get_value?ref=${transferServicePath}/1/result/code`)
+          .body.toString('utf-8')).result
+        expect(fromAfterBalance).to.equal(fromBeforeBalance - transferAmount);
+        expect(toServiceAfterBalance).to.equal(toServiceBeforeBalance + transferAmount);
+        expect(resultCode).to.equal(FunctionResultCode.SUCCESS);
+      });
+
+      it('transfer: transfer with invalid service account service type', async () => {
+        let fromBeforeBalance = parseOrLog(syncRequest('GET',
+            server2 + `/get_value?ref=${transferFromBalancePath}`).body.toString('utf-8')).result;
+        const transferToService = `invalid_service_type|test_service|${transferTo}|0`;
+        const transferServicePath = `/transfer/${transferFrom}/${transferToService}`;
+        const body = parseOrLog(syncRequest('POST', server1 + '/set_value', {json: {
+          ref: transferServicePath + '/1/value',
+          value: transferAmount,
+          nonce: -1,
+          timestamp: 1234567890000,
+        }}).body.toString('utf-8'));
+        assert.deepEqual(body, {
+          "code": 1,
+          "result": {
+            "result": {
+              "code": 103,
+              "error_message": "No .write permission on: /transfer/0x00ADEc28B6a845a085e03591bE7550dd68673C1C/invalid_service_type|test_service|0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204|0/1/value",
+              "gas_amount": 0,
+              "gas_amount_total": {
+                "app": {},
+                "service": 0
+              },
+              "gas_cost_total": 0
+            },
+            "tx_hash": "0x6cce46b284beb254c6b67205f5ba00f04c85028d7457410b4fa4b4d8522c14be"
+          }
+        });
+        const fromAfterBalance = parseOrLog(syncRequest('GET',
+            server1 + `/get_value?ref=${transferFromBalancePath}`).body.toString('utf-8')).result;
+        expect(fromAfterBalance).to.equal(fromBeforeBalance);
+      });
     })
 
     describe('Staking: _stake, _unstake', () => {
@@ -5593,4 +5823,368 @@ describe('Blockchain Node', () => {
       });
     });
   });
+
+  describe('Billing', async () => {
+    let serviceAdmin; // = server1
+    let billingUserA; // = server2
+    let billingUserB; // = server3
+    let userBalancePathA;
+    let userBalancePathB;
+    const billingAccountBalancePathA = '/get_value?ref=/service_accounts/billing/test_billing/A/balance';
+    before(async () => {
+      serviceAdmin =
+          parseOrLog(syncRequest('GET', server1 + '/get_address').body.toString('utf-8')).result;
+      billingUserA =
+          parseOrLog(syncRequest('GET', server2 + '/get_address').body.toString('utf-8')).result;
+      billingUserB =
+          parseOrLog(syncRequest('GET', server3 + '/get_address').body.toString('utf-8')).result;
+      userBalancePathA = `/get_value?ref=/accounts/${billingUserA}/balance`;
+      userBalancePathB = `/get_value?ref=/accounts/${billingUserB}/balance`;
+
+      const appStakingRes = parseOrLog(syncRequest('POST', server1 + '/set_value', {json: {
+        ref: `/staking/test_billing/${serviceAdmin}/0/stake/${Date.now()}/value`,
+        value: 1
+      }}).body.toString('utf-8')).result;
+      if (!(await waitUntilTxFinalized(serverList, appStakingRes.tx_hash))) {
+        console.error(`Failed to check finalization of app staking tx.`);
+      }
+
+      const createAppRes = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: {
+        ref: '/manage_app/test_billing/create/0',
+        value: {
+          admin: {
+            [serviceAdmin]: true,
+            [billingUserA]: true,
+            [billingUserB]: true
+          },
+          billing: {
+            A: { 
+              users: {
+                [serviceAdmin]: true,
+                [billingUserA]: true
+              }
+            },
+            B: {
+              users: {
+                [serviceAdmin]: true,
+                [billingUserB]: true
+              }
+            }
+          }
+        },
+        nonce: -1,
+        timestamp: Date.now(),
+      }}).body.toString('utf-8')).result;
+      if (!(await waitUntilTxFinalized(serverList, createAppRes.tx_hash))) {
+        console.error(`Failed to check finalization of create app tx.`);
+      }
+
+      const server4Addr =
+          parseOrLog(syncRequest('GET', server4 + '/get_address').body.toString('utf-8')).result;
+      const transferRes = parseOrLog(syncRequest('POST', server4 + '/set', {json: {
+        op_list: [
+          {
+            ref: `/transfer/${server4Addr}/billing|test_billing|A/${Date.now()}/value`,
+            value: 100,
+            type: 'SET_VALUE'
+          },
+          {
+            ref: `/transfer/${server4Addr}/billing|test_billing|B/${Date.now()}/value`,
+            value: 100,
+            type: 'SET_VALUE'
+          }
+        ],
+        nonce: -1,
+        timestamp: Date.now(),
+      }}).body.toString('utf-8')).result;
+      if (!(await waitUntilTxFinalized(serverList, transferRes.tx_hash))) {
+        console.error(`Failed to check finalization of transfer tx.`);
+      }
+    });
+
+    it('app txs are not charged by transfer', async () => {
+      const balanceBefore = parseOrLog(syncRequest('GET', server2 + userBalancePathA).body.toString('utf-8')).result;
+      const txWithoutBillingRes = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: {
+          ref: '/apps/test_billing/test',
+          value: 'testing app tx',
+          gas_price: 1,
+          nonce: -1,
+          timestamp: Date.now(),
+        }
+      }).body.toString('utf-8')).result;
+      if (!(await waitUntilTxFinalized(serverList, txWithoutBillingRes.tx_hash))) {
+        console.error(`Failed to check finalization of app tx.`);
+      }
+      const balanceAfter = parseOrLog(syncRequest('GET', server2 + userBalancePathA).body.toString('utf-8')).result;
+      assert.deepEqual(balanceAfter, balanceBefore);
+
+      const billingAccountBalanceBefore = parseOrLog(syncRequest(
+          'GET', server2 + billingAccountBalancePathA).body.toString('utf-8')).result;
+      const txWithBillingRes = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: {
+          ref: '/apps/test_billing/test',
+          value: 'testing app tx',
+          gas_price: 1,
+          billing: 'test_billing|A',
+          nonce: -1,
+          timestamp: Date.now(),
+        }
+      }).body.toString('utf-8')).result;
+      if (!(await waitUntilTxFinalized(serverList, txWithBillingRes.tx_hash))) {
+        console.error(`Failed to check finalization of app tx.`);
+      }
+      const billingAccountBalanceAfter = parseOrLog(syncRequest(
+          'GET', server2 + billingAccountBalancePathA).body.toString('utf-8')).result;
+      assert.deepEqual(billingAccountBalanceAfter, billingAccountBalanceBefore);
+    });
+
+    it('app-dependent service tx: individual account', async () => {
+      const gasPrice = 1;
+      const txRes = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: {
+          ref: '/manage_app/test_billing/config/service/staking/lockup_duration',
+          value: 1000,
+          gas_price: gasPrice,
+          nonce: -1,
+          timestamp: Date.now(),
+        }
+      }).body.toString('utf-8')).result;
+      if (!(await waitUntilTxFinalized(serverList, txRes.tx_hash))) {
+        console.error(`Failed to check finalization of app tx.`);
+      }
+      // NOTE(liayoo): Checking the gas fee was collected instead of account balances, since the
+      // nodes also participate in consensus & get the collected fees as rewards.
+      const tx = parseOrLog(syncRequest('GET', server2 + `/get_transaction?hash=${txRes.tx_hash}`).body.toString('utf-8')).result;
+      const gasFeeCollected = parseOrLog(syncRequest(
+        'GET',
+        `${server2}/get_value?ref=/gas_fee/collect/${billingUserA}/${tx.number}/${txRes.tx_hash}/amount`
+      ).body.toString('utf-8')).result;
+      assert.deepEqual(gasFeeCollected, gasPrice * MICRO_AIN * txRes.result.gas_amount_total.service);
+    });
+
+    it('app-dependent service tx: invalid billing param', async () => {
+      const txResBody = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: {
+          ref: '/manage_app/test_billing/config/service/staking/lockup_duration',
+          value: 1000,
+          gas_price: 1,
+          billing: 'A',
+          nonce: -1,
+          timestamp: Date.now(),
+        }
+      }).body.toString('utf-8'));
+      assert.deepEqual(txResBody, {code: 1, result: { tx_hash: null, result: false }});
+    });
+
+    it('app-dependent service tx: not a billing account user', async () => {
+      const txResBody = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: {
+          ref: '/manage_app/test_billing/config/service/staking/lockup_duration',
+          value: 1000,
+          gas_price: 1,
+          billing: 'test_billing|B',
+          nonce: -1,
+          timestamp: Date.now(),
+        }
+      }).body.toString('utf-8'));
+      expect(txResBody.code).to.equals(1);
+      expect(txResBody.result.result.code, 18);
+      expect(txResBody.result.result.error_message.includes('No .write permission on: /gas_fee/collect/billing|test_billing|B'), true);
+    });
+
+    it('app-dependent service tx: billing account', async () => {
+      const billingAccountBalanceBefore = parseOrLog(syncRequest(
+        'GET', server2 + billingAccountBalancePathA).body.toString('utf-8')).result;
+      const gasPrice = 1;
+      const txRes = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: {
+          ref: '/manage_app/test_billing/config/service/staking/lockup_duration',
+          value: 1000,
+          gas_price: 1,
+          billing: 'test_billing|A',
+          nonce: -1,
+          timestamp: Date.now(),
+        }
+      }).body.toString('utf-8')).result;
+      if (!(await waitUntilTxFinalized(serverList, txRes.tx_hash))) {
+        console.error(`Failed to check finalization of app tx.`);
+      }
+      const billingAccountBalanceAfter = parseOrLog(syncRequest(
+          'GET', server2 + billingAccountBalancePathA).body.toString('utf-8')).result;
+      assert.deepEqual(
+        billingAccountBalanceAfter,
+        billingAccountBalanceBefore - (gasPrice * MICRO_AIN * txRes.result.gas_amount_total.service)
+      );
+    });
+
+    it('app-independent service tx: individual account', async () => {
+      const gasPrice = 1;
+      const txRes = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: {
+          ref: `/transfer/${billingUserA}/${billingUserB}/${Date.now()}/value`,
+          value: 1,
+          gas_price: gasPrice,
+          nonce: -1,
+          timestamp: Date.now(),
+        }
+      }).body.toString('utf-8')).result;
+      if (!(await waitUntilTxFinalized(serverList, txRes.tx_hash))) {
+        console.error(`Failed to check finalization of app tx.`);
+      }
+      // NOTE(liayoo): Checking the gas fee was collected instead of account balances, since the
+      // nodes also participate in consensus & get the collected fees as rewards.
+      const tx = parseOrLog(syncRequest('GET', server2 + `/get_transaction?hash=${txRes.tx_hash}`).body.toString('utf-8')).result;
+      const gasFeeCollected = parseOrLog(syncRequest(
+        'GET',
+        `${server2}/get_value?ref=/gas_fee/collect/${billingUserA}/${tx.number}/${txRes.tx_hash}/amount`
+      ).body.toString('utf-8')).result;
+      assert.deepEqual(gasFeeCollected, gasPrice * MICRO_AIN * txRes.result.gas_amount_total.service);
+    });
+
+    it('app-independent service tx: billing account', async () => {
+      const billingAccountBalanceBefore = parseOrLog(syncRequest(
+          'GET', server2 + billingAccountBalancePathA).body.toString('utf-8')).result;
+      const gasPrice = 1;
+      const txRes = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: {
+          ref: `/transfer/${billingUserA}/${billingUserB}/${Date.now()}/value`,
+          value: 1,
+          gas_price: gasPrice,
+          billing: 'test_billing|A',
+          nonce: -1,
+          timestamp: Date.now(),
+        }
+      }).body.toString('utf-8')).result;
+      if (!(await waitUntilTxFinalized(serverList, txRes.tx_hash))) {
+        console.error(`Failed to check finalization of app tx.`);
+      }
+      const billingAccountBalanceAfter = parseOrLog(syncRequest(
+          'GET', server2 + billingAccountBalancePathA).body.toString('utf-8')).result;
+      assert.deepEqual(
+        billingAccountBalanceAfter,
+        billingAccountBalanceBefore - (gasPrice * MICRO_AIN * txRes.result.gas_amount_total.service)
+      );
+    });
+
+    it('multi-set service tx: individual account', async () => {
+      const gasPrice = 1;
+      const txRes = parseOrLog(syncRequest('POST', server2 + '/set', {json: {
+          op_list: [
+            {
+              ref: `/transfer/${billingUserA}/${billingUserB}/${Date.now()}/value`,
+              value: 1,
+              type: 'SET_VALUE'
+            },
+            {
+              ref: `/manage_app/test_billing/config/service/staking/lockup_duration`,
+              value: 100,
+              type: 'SET_VALUE'
+            }
+          ],
+          gas_price: gasPrice,
+          nonce: -1,
+          timestamp: Date.now(),
+        }
+      }).body.toString('utf-8')).result;
+      if (!(await waitUntilTxFinalized(serverList, txRes.tx_hash))) {
+        console.error(`Failed to check finalization of app tx.`);
+      }
+      // NOTE(liayoo): Checking the gas fee was collected instead of account balances, since the
+      // nodes also participate in consensus & get the collected fees as rewards.
+      const tx = parseOrLog(syncRequest('GET', server2 + `/get_transaction?hash=${txRes.tx_hash}`).body.toString('utf-8')).result;
+      const gasFeeCollected = parseOrLog(syncRequest(
+        'GET',
+        `${server2}/get_value?ref=/gas_fee/collect/${billingUserA}/${tx.number}/${txRes.tx_hash}/amount`
+      ).body.toString('utf-8')).result;
+      assert.deepEqual(gasFeeCollected, gasPrice * MICRO_AIN * txRes.result.gas_amount_total.service);
+    });
+
+    it('multi-set service tx: billing account', async () => {
+      const billingAccountBalanceBefore = parseOrLog(syncRequest(
+          'GET', server2 + billingAccountBalancePathA).body.toString('utf-8')).result;
+      const gasPrice = 1;
+      const txRes = parseOrLog(syncRequest('POST', server2 + '/set', {json: {
+          op_list: [
+            {
+              ref: `/transfer/${billingUserA}/${billingUserB}/${Date.now()}/value`,
+              value: 1,
+              type: 'SET_VALUE'
+            },
+            {
+              ref: `/manage_app/test_billing/config/service/staking/lockup_duration`,
+              value: 100,
+              type: 'SET_VALUE'
+            }
+          ],
+          billing: 'test_billing|A',
+          gas_price: gasPrice,
+          nonce: -1,
+          timestamp: Date.now(),
+        }
+      }).body.toString('utf-8')).result;
+      if (!(await waitUntilTxFinalized(serverList, txRes.tx_hash))) {
+        console.error(`Failed to check finalization of app tx.`);
+      }
+      const billingAccountBalanceAfter = parseOrLog(syncRequest(
+          'GET', server2 + billingAccountBalancePathA).body.toString('utf-8')).result;
+      assert.deepEqual(
+        billingAccountBalanceAfter,
+        billingAccountBalanceBefore - (gasPrice * MICRO_AIN * txRes.result.gas_amount_total.service)
+      );
+    });
+
+    it('multi-set service tx: multiple apps', async () => {
+      // Set up another app
+      const appStakingRes = parseOrLog(syncRequest('POST', server1 + '/set_value', {json: {
+        ref: `/staking/test_billing_2/${serviceAdmin}/0/stake/${Date.now()}/value`,
+        value: 1
+      }}).body.toString('utf-8')).result;
+      if (!(await waitUntilTxFinalized(serverList, appStakingRes.tx_hash))) {
+        console.error(`Failed to check finalization of app staking tx.`);
+      }
+
+      const createAppRes = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: {
+        ref: '/manage_app/test_billing_2/create/0',
+        value: {
+          admin: {
+            [serviceAdmin]: true,
+            [billingUserA]: true,
+            [billingUserB]: true
+          },
+          billing: {
+            '0': { 
+              users: {
+                [serviceAdmin]: true,
+                [billingUserA]: true,
+                [billingUserB]: true
+              }
+            }
+          }
+        },
+        nonce: -1,
+        timestamp: Date.now(),
+      }}).body.toString('utf-8')).result;
+      if (!(await waitUntilTxFinalized(serverList, createAppRes.tx_hash))) {
+        console.error(`Failed to check finalization of create app tx.`);
+      }
+
+      const txResBody = parseOrLog(syncRequest('POST', server1 + '/set', {json: {
+          op_list: [
+            {
+              ref: `/manage_app/test_billing/config/service/staking/lockup_duration`,
+              value: 100,
+              type: 'SET_VALUE'
+            },
+            {
+              ref: `/manage_app/test_billing_2/config/service/staking/lockup_duration`,
+              value: 100,
+              type: 'SET_VALUE'
+            }
+          ],
+          billing: 'test_billing|A',
+          gas_price: 1,
+          nonce: -1,
+          timestamp: Date.now(),
+        }
+      }).body.toString('utf-8'));
+      assert.deepEqual(txResBody.result.result, {
+        "error_message": "Failed to collect gas fee: Multiple app-dependent service operations for a billing account",
+        "code": 16,
+        "gas_amount": 0
+      });
+    });
+  });
 });
diff --git a/json_rpc/index.js b/json_rpc/index.js
index b86bc96f5..d6a63bd33 100644
--- a/json_rpc/index.js
+++ b/json_rpc/index.js
@@ -48,7 +48,7 @@ module.exports = function getMethods(node, p2pServer, minProtocolVersion, maxPro
 
     // Bloock API
     ain_getBlockList: function(args, done) {
-      const blocks = node.bc.getChainSection(args.from, args.to);
+      const blocks = node.bc.getBlockList(args.from, args.to);
       done(null, addProtocolVersion({result: blocks}));
     },
 
@@ -62,7 +62,7 @@ module.exports = function getMethods(node, p2pServer, minProtocolVersion, maxPro
     },
 
     ain_getBlockHeadersList: function(args, done) {
-      const blocks = node.bc.getChainSection(args.from, args.to);
+      const blocks = node.bc.getBlockList(args.from, args.to);
       const blockHeaders = [];
       blocks.forEach((block) => {
         blockHeaders.push(block.header);
@@ -215,7 +215,9 @@ module.exports = function getMethods(node, p2pServer, minProtocolVersion, maxPro
         if (transactionInfo.status === TransactionStatus.BLOCK_STATUS) {
           const block = node.bc.getBlockByNumber(transactionInfo.number);
           const index = transactionInfo.index;
-          if (index >= 0) {
+          if (!block) {
+            // TODO(liayoo): Ask peers for the transaction / block
+          } else if (index >= 0) {
             transactionInfo.transaction = block.transactions[index];
           } else {
             transactionInfo.transaction = _.find(block.last_votes, (tx) => tx.hash === args.hash);
@@ -233,7 +235,7 @@ module.exports = function getMethods(node, p2pServer, minProtocolVersion, maxPro
       if (args.block_hash && Number.isInteger(args.index)) {
         const index = Number(args.index);
         const block = node.bc.getBlockByHash(args.block_hash);
-        if (block.transactions.length > index && index >= 0) {
+        if (block && block.transactions.length > index && index >= 0) {
           result = {
             transaction: block.transactions[index],
             is_finalized: true
@@ -248,7 +250,7 @@ module.exports = function getMethods(node, p2pServer, minProtocolVersion, maxPro
       if (Number.isInteger(args.block_number) && Number.isInteger(args.index)) {
         const index = Number(args.index);
         const block = node.bc.getBlockByNumber(args.block_number);
-        if (block.transactions.length > index && index >= 0) {
+        if (block && block.transactions.length > index && index >= 0) {
           result = {
             transaction: block.transactions[index],
             is_finalized: true
diff --git a/node/index.js b/node/index.js
index f99fd2a35..347e544ef 100644
--- a/node/index.js
+++ b/node/index.js
@@ -1,13 +1,19 @@
 /* eslint guard-for-in: "off" */
 const ainUtil = require('@ainblockchain/ain-util');
 const _ = require('lodash');
+const fs = require('fs');
+const path = require('path');
 const logger = require('../logger')('NODE');
 const {
   FeatureFlags,
   PORT,
   ACCOUNT_INDEX,
+  SYNC_MODE,
   TX_NONCE_ERROR_CODE,
   TX_TIMESTAMP_ERROR_CODE,
+  SNAPSHOTS_ROOT_DIR,
+  SNAPSHOTS_INTERVAL_BLOCK_NUMBER,
+  MAX_NUM_SNAPSHOTS,
   BlockchainNodeStates,
   PredefinedDbPaths,
   ShardingProperties,
@@ -15,8 +21,10 @@ const {
   GenesisAccounts,
   GenesisSharding,
   StateVersions,
+  SyncModeOptions,
   LIGHTWEIGHT
 } = require('../common/constants');
+const FileUtil = require('../common/file-util');
 const ChainUtil = require('../common/chain-util');
 const Blockchain = require('../blockchain');
 const TransactionPool = require('../tx-pool');
@@ -51,6 +59,8 @@ class BlockchainNode {
     this.db = this.createDb(StateVersions.EMPTY, initialVersion, this.bc, this.tp, false, true);
     this.nonce = null;  // nonce from current final version
     this.state = BlockchainNodeStates.STARTING;
+    this.snapshotDir = path.resolve(SNAPSHOTS_ROOT_DIR, `${PORT}`);
+    FileUtil.createSnapshotDir(this.snapshotDir);
   }
 
   // For testing purpose only.
@@ -75,23 +85,51 @@ class BlockchainNode {
 
   init(isFirstNode) {
     const LOG_HEADER = 'init';
-
     logger.info(`[${LOG_HEADER}] Initializing node..`);
-    const lastBlockWithoutProposal = this.bc.init(isFirstNode);
+    let latestSnapshot = null;
+    let latestSnapshotPath = null;
+    let latestSnapshotBlockNumber = -1;
+
+    // 1. Get the latest snapshot if in the "fast" sync mode.
+    if (SYNC_MODE === SyncModeOptions.FAST) {
+      const latestSnapshotInfo = FileUtil.getLatestSnapshotInfo(this.snapshotDir);
+      latestSnapshotPath = latestSnapshotInfo.latestSnapshotPath;
+      latestSnapshotBlockNumber = latestSnapshotInfo.latestSnapshotBlockNumber;
+      if (latestSnapshotPath) {
+        try {
+          latestSnapshot = FileUtil.readCompressedJson(latestSnapshotPath);
+        } catch (err) {
+          logger.error(`[${LOG_HEADER}] ${err.stack}`);
+        }
+      }
+    }
+
+    // 2. Initialize the blockchain, starting from `latestSnapshotBlockNumber`.
+    const lastBlockWithoutProposal = this.bc.init(isFirstNode, latestSnapshotBlockNumber);
+
+    // 3. Initialize DB (with the latest snapshot, if it exists)
     const startingDb =
         this.createDb(StateVersions.EMPTY, StateVersions.START, this.bc, this.tp, true);
-    startingDb.initDbStates();
+    startingDb.initDbStates(latestSnapshot);
+
+    // 4. Execute the chain on the DB and finalize it.
     this.executeChainOnDb(startingDb);
-    this.nonce = this.getNonceFromChain();
     this.cloneAndFinalizeVersion(StateVersions.START, this.bc.lastBlockNumber());
+    this.nonce = this.getNonceForAddr(this.account.address, false, true);
+
+    // 5. Execute transactions from the pool.
     this.db.executeTransactionList(
         this.tp.getValidTransactions(null, this.stateManager.getFinalVersion()),
         this.bc.lastBlockNumber() + 1);
+
+    // 6. Node status changed: STARTING -> SYNCING.
     this.state = BlockchainNodeStates.SYNCING;
+
     return lastBlockWithoutProposal;
   }
 
   createTempDb(baseVersion, versionPrefix, blockNumberSnapshot) {
+    const LOG_HEADER = 'createTempDb';
     const { tempVersion, tempRoot } = this.stateManager.cloneToTempVersion(
         baseVersion, versionPrefix);
     if (!tempRoot) {
@@ -175,44 +213,30 @@ class BlockchainNode {
     }
     const nodeVersion = `${StateVersions.NODE}:${blockNumber}`;
     this.syncDbAndNonce(nodeVersion);
+    this.updateSnapshots(blockNumber);
   }
 
-  dumpFinalVersion(withDetails) {
-    return this.stateManager.getFinalRoot().toJsObject(withDetails);
-  }
-
-  getNonceFromChain() {
-    const LOG_HEADER = 'getNonceFromChain';
-
-    // TODO(cshcomcom): Search through all blocks for any previous nonced transaction with current
-    // account.
-    let nonce = 0;
-    for (let i = this.bc.chain.length - 1; i > -1; i--) {
-      for (let j = this.bc.chain[i].transactions.length - 1; j > -1; j--) {
-        if (ChainUtil.areSameAddrs(this.bc.chain[i].transactions[j].address,
-            this.account.address) && this.bc.chain[i].transactions[j].tx_body.nonce > -1) {
-          // If blockchain is being restarted, retreive nonce from blockchain
-          nonce = this.bc.chain[i].transactions[j].tx_body.nonce + 1;
-          break;
-        }
-      }
-      if (nonce > 0) {
-        break;
-      }
+  updateSnapshots(blockNumber) {
+    if (blockNumber > 0 && blockNumber % SNAPSHOTS_INTERVAL_BLOCK_NUMBER === 0) {
+      const snapshot = this.dumpFinalVersion(false);
+      FileUtil.writeSnapshot(this.snapshotDir, blockNumber, snapshot);
+      FileUtil.writeSnapshot(
+          this.snapshotDir, blockNumber - MAX_NUM_SNAPSHOTS * SNAPSHOTS_INTERVAL_BLOCK_NUMBER, null);
     }
+  }
 
-    logger.info(`[${LOG_HEADER}] Setting nonce to ${nonce}`);
-    return nonce;
+  dumpFinalVersion(withDetails) {
+    return this.stateManager.getFinalRoot().toJsObject(withDetails);
   }
 
-  getNonceForAddr(address, fromPending) {
+  getNonceForAddr(address, fromPending, fromDb = false) {
     if (!isValAddr(address)) return -1;
     const cksumAddr = toCksumAddr(address);
     if (fromPending) {
       const { nonce } = this.db.getAccountNonceAndTimestamp(cksumAddr);
       return nonce;
     }
-    if (cksumAddr === this.account.address) {
+    if (!fromDb && cksumAddr === this.account.address) {
       return this.nonce;
     }
     const stateRoot = this.stateManager.getFinalRoot();
@@ -428,7 +452,7 @@ class BlockchainNode {
       logger.error(`Failed to create a temp database with state version: ${baseVersion}.`);
       return null;
     }
-    const validBlocks = this.bc.getValidBlocks(chainSegment);
+    const validBlocks = this.bc.getValidBlocksInChainSegment(chainSegment);
     if (validBlocks.length > 0) {
       if (!this.applyBlocksToDb(validBlocks, tempDb)) {
         logger.error(`[${LOG_HEADER}] Failed to apply valid blocks to database: ` +
@@ -461,15 +485,22 @@ class BlockchainNode {
   executeChainOnDb(db) {
     const LOG_HEADER = 'executeChainOnDb';
 
-    this.bc.chain.forEach((block) => {
+    for (const block of this.bc.chain) {
       if (!db.executeTransactionList(block.last_votes)) {
-        logger.error(`[${LOG_HEADER}] Failed to execute last_votes`)
+        logger.error(`[${LOG_HEADER}] Failed to execute last_votes (${block.number})`);
+        process.exit(1); // NOTE(liayoo): Quick fix for the problem. May be fixed by deleting the block files.
       }
       if (!db.executeTransactionList(block.transactions, block.number)) {
-        logger.error(`[${LOG_HEADER}] Failed to execute transactions`)
+        logger.error(`[${LOG_HEADER}] Failed to execute transactions (${block.number})`)
+        process.exit(1); // NOTE(liayoo): Quick fix for the problem. May be fixed by deleting the block files.
+      }
+      if (block.state_proof_hash !== db.stateRoot.getProofHash()) {
+        logger.error(`[${LOG_HEADER}] Invalid state proof hash (${block.number}): ` +
+            `${db.stateRoot.getProofHash()}, ${block.state_proof_hash}`);
+        process.exit(1); // NOTE(liayoo): Quick fix for the problem. May be fixed by deleting the block files.
       }
       this.tp.cleanUpForNewBlock(block);
-    });
+    }
   }
 }
 
diff --git a/p2p/index.js b/p2p/index.js
index 409abd2d7..e74e93d84 100644
--- a/p2p/index.js
+++ b/p2p/index.js
@@ -1,9 +1,7 @@
 /* eslint no-mixed-operators: "off" */
 const _ = require('lodash');
 const P2pServer = require('./server');
-const url = require('url');
 const Websocket = require('ws');
-const semver = require('semver');
 const logger = require('../logger')('P2P_CLIENT');
 const { ConsensusStatus } = require('../consensus/constants');
 const VersionUtil = require('../common/version-util');
@@ -16,8 +14,7 @@ const {
   DEFAULT_MAX_OUTBOUND,
   DEFAULT_MAX_INBOUND,
   MAX_OUTBOUND_LIMIT,
-  MAX_INBOUND_LIMIT,
-  FeatureFlags
+  MAX_INBOUND_LIMIT
 } = require('../common/constants');
 const { sleep } = require('../common/chain-util');
 const {
@@ -46,8 +43,8 @@ class P2pClient {
     this.startHeartbeat();
   }
 
-  run() {
-    this.server.listen();
+  async run() {
+    await this.server.listen();
     this.connectToTracker();
   }
 
@@ -97,31 +94,26 @@ class P2pClient {
   }
 
   getNetworkStatus() {
+    const extIp = this.server.getExternalIp();
+    const url = new URL(`ws://${extIp}:${P2P_PORT}`);
+    const p2pUrl = url.toString();
+    url.protocol = 'http:';
+    url.port = PORT;
+    const clientApiUrl = url.toString();
+    url.pathname = 'json-rpc';
+    const jsonRpcUrl = url.toString();
     return {
-      ip: this.server.getExternalIp(),
+      ip: extIp,
       p2p: {
-        url: url.format({
-          protocol: 'ws',
-          hostname: this.server.getExternalIp(),
-          port: P2P_PORT
-        }),
+        url: p2pUrl,
         port: P2P_PORT,
       },
       clientApi: {
-        url: url.format({
-          protocol: 'http',
-          hostname: this.server.getExternalIp(),
-          port: PORT
-        }),
+        url: clientApiUrl,
         port: PORT,
       },
       jsonRpc: {
-        url: url.format({
-          protocol: 'http',
-          hostname: this.server.getExternalIp(),
-          port: PORT,
-          pathname: '/json-rpc',
-        }),
+        url: jsonRpcUrl,
         port: PORT,
       },
       connectionStatus: this.getConnectionStatus()
@@ -165,7 +157,6 @@ class P2pClient {
           `${JSON.stringify(this.server.managedPeersInfo, null, 2)}`);
       }
       if (node.state === BlockchainNodeStates.STARTING) {
-        node.state = BlockchainNodeStates.SYNCING;
         if (parsedMsg.numLivePeers === 0) {
           const lastBlockWithoutProposal = node.init(true);
           await this.server.tryInitializeShard();
@@ -214,9 +205,12 @@ class P2pClient {
     logger.debug(`SENDING: ${JSON.stringify(consensusMessage)}`);
   }
 
-  requestChainSegment(socket, lastBlock) {
-    const payload = encapsulateMessage(MessageTypes.CHAIN_SEGMENT_REQUEST,
-        { lastBlock: lastBlock });
+  requestChainSegment(socket, lastBlockNumber) {
+    if (this.server.node.state !== BlockchainNodeStates.SYNCING &&
+      this.server.node.state !== BlockchainNodeStates.SERVING) {
+      return;
+    }
+    const payload = encapsulateMessage(MessageTypes.CHAIN_SEGMENT_REQUEST, { lastBlockNumber });
     if (!payload) {
       logger.error('The request chainSegment cannot be sent because of msg encapsulation failure.');
       return;
@@ -256,36 +250,6 @@ class P2pClient {
     socket.send(JSON.stringify(payload));
   }
 
-  // TODO(minsulee2): This check will be updated when data compatibility version up.
-  checkDataProtoVerForAddressResponse(version) {
-    const majorVersion = VersionUtil.toMajorVersion(version);
-    const isGreater = semver.gt(this.server.majorDataProtocolVersion, majorVersion);
-    if (isGreater) {
-      // TODO(minsulee2): Compatible message.
-    }
-    const isLower = semver.lt(this.server.majorDataProtocolVersion, majorVersion);
-    if (isLower) {
-      // TODO(minsulee2): Compatible message.
-    }
-  }
-
-  checkDataProtoVerForChainSegmentResponse(version) {
-    const majorVersion = VersionUtil.toMajorVersion(version);
-    const isGreater = semver.gt(this.server.majorDataProtocolVersion, majorVersion);
-    if (isGreater) {
-      // TODO(minsulee2): Compatible message.
-    }
-    const isLower = semver.lt(this.server.majorDataProtocolVersion, majorVersion);
-    if (isLower) {
-      if (FeatureFlags.enableRichP2pCommunicationLogging) {
-        logger.error('CANNOT deal with higher data protocol version. Discard the ' +
-          'CHAIN_SEGMENT_RESPONSE message.');
-      }
-      return false;
-    }
-    return true;
-  }
-
   setPeerEventHandlers(socket) {
     const LOG_HEADER = 'setPeerEventHandlers';
     socket.on('message', (message) => {
@@ -307,8 +271,12 @@ class P2pClient {
 
       switch (parsedMessage.type) {
         case MessageTypes.ADDRESS_RESPONSE:
-          // TODO(minsulee2): Add compatibility check here after data version up.
-          // this.checkDataProtoVerForAddressResponse(dataProtoVer);
+          const dataVersionCheckForAddress =
+              this.server.checkDataProtoVer(dataProtoVer, MessageTypes.ADDRESS_RESPONSE);
+          if (dataVersionCheckForAddress < 0) {
+            // TODO(minsulee2): need to convert message when updating ADDRESS_RESPONSE necessary.
+            // this.convertAddressMessage();
+          }
           const address = _.get(parsedMessage, 'data.body.address');
           if (!address) {
             logger.error(`[${LOG_HEADER}] Providing an address is compulsary when initiating ` +
@@ -342,8 +310,21 @@ class P2pClient {
           }
           break;
         case MessageTypes.CHAIN_SEGMENT_RESPONSE:
-          if (!this.checkDataProtoVerForChainSegmentResponse(parsedMessage.dataProtoVer)) {
+          if (this.server.node.state !== BlockchainNodeStates.SYNCING &&
+              this.server.node.state !== BlockchainNodeStates.SERVING) {
+            logger.error(`[${LOG_HEADER}] Not ready to process chain segment response.\n` +
+                `Node state: ${this.server.node.state}.`);
+            return;
+          }
+          const dataVersionCheckForChainSegment =
+              this.server.checkDataProtoVer(dataProtoVer, MessageTypes.CHAIN_SEGMENT_RESPONSE);
+          if (dataVersionCheckForChainSegment > 0) {
+            logger.error(`[${LOG_HEADER}] CANNOT deal with higher data protocol ` +
+                `version(${dataProtoVer}). Discard the CHAIN_SEGMENT_RESPONSE message.`);
             return;
+          } else if (dataVersionCheckForChainSegment < 0) {
+            // TODO(minsulee2): need to convert message when updating CHAIN_SEGMENT_RESPONSE.
+            // this.convertChainSegmentResponseMessage();
           }
           const chainSegment = _.get(parsedMessage, 'data.chainSegment');
           const number = _.get(parsedMessage, 'data.number');
@@ -394,7 +375,7 @@ class P2pClient {
             // your local blockchain matches the height of the consensus blockchain.
             if (number > this.server.node.bc.lastBlockNumber()) {
               setTimeout(() => {
-                this.requestChainSegment(socket, this.server.node.bc.lastBlock());
+                this.requestChainSegment(socket, this.server.node.bc.lastBlockNumber());
               }, 1000);
             }
           } else {
@@ -413,13 +394,13 @@ class P2pClient {
               logger.info(`[${LOG_HEADER}] I am behind ` +
                   `(${number} < ${this.server.node.bc.lastBlockNumber()}).`);
               setTimeout(() => {
-                this.requestChainSegment(socket, this.server.node.bc.lastBlock());
+                this.requestChainSegment(socket, this.server.node.bc.lastBlockNumber());
               }, 1000);
             }
           }
           break;
         default:
-          logger.error(`[${LOG_HEADER}] Wrong message type(${parsedMessage.type}) has been ` +
+          logger.error(`[${LOG_HEADER}] Unknown message type(${parsedMessage.type}) has been ` +
               `specified. Igonore the message.`);
           break;
       }
@@ -464,7 +445,7 @@ class P2pClient {
           this.setPeerEventHandlers(socket);
           this.sendAddress(socket);
           await this.waitForAddress(socket);
-          this.requestChainSegment(socket, this.server.node.bc.lastBlock());
+          this.requestChainSegment(socket, this.server.node.bc.lastBlockNumber());
           if (this.server.consensus.stakeTx) {
             this.broadcastTransaction(this.server.consensus.stakeTx);
             this.server.consensus.stakeTx = null;
diff --git a/p2p/server.js b/p2p/server.js
index 9b174056d..ac2929c98 100644
--- a/p2p/server.js
+++ b/p2p/server.js
@@ -3,15 +3,14 @@ const Websocket = require('ws');
 const ip = require('ip');
 const publicIp = require('public-ip');
 const axios = require('axios');
-const semver = require('semver');
 const disk = require('diskusage');
 const os = require('os');
 const v8 = require('v8');
 const _ = require('lodash');
+const semver = require('semver');
 const ainUtil = require('@ainblockchain/ain-util');
 const logger = require('../logger')('P2P_SERVER');
 const Consensus = require('../consensus');
-const { Block } = require('../blockchain/block');
 const Transaction = require('../tx-pool/transaction');
 const VersionUtil = require('../common/version-util');
 const {
@@ -76,7 +75,7 @@ class P2pServer {
     this.maxInbound = maxInbound;
   }
 
-  listen() {
+  async listen() {
     this.wsServer = new Websocket.Server({
       port: P2P_PORT,
       // Enables server-side compression. For option details, see
@@ -106,7 +105,7 @@ class P2pServer {
       this.setPeerEventHandlers(socket);
     });
     logger.info(`Listening to peer-to-peer connections on: ${P2P_PORT}\n`);
-    this.setUpIpAddresses().then(() => { });
+    await this.setUpIpAddresses();
   }
 
   getNodeAddress() {
@@ -323,50 +322,27 @@ class P2pServer {
     });
   }
 
-  // TODO(minsulee2): This check will be updated when data compatibility version up.
-  checkDataProtoVerForAddressRequest(version) {
-    const majorVersion = VersionUtil.toMajorVersion(version);
-    const isGreater = semver.gt(this.majorDataProtocolVersion, majorVersion);
-    if (isGreater) {
-      // TODO(minsulee2): Compatible message.
-    }
-    const isLower = semver.lt(this.majorDataProtocolVersion, majorVersion);
-    if (isLower) {
-      // TODO(minsulee2): Compatible message.
-    }
-  }
-
-  checkDataProtoVerForConsensus(version) {
-    const majorVersion = VersionUtil.toMajorVersion(version);
-    const isGreater = semver.gt(this.majorDataProtocolVersion, majorVersion);
-    if (isGreater) {
-      // TODO(minsulee2): Compatible message.
-    }
-    const isLower = semver.lt(this.majorDataProtocolVersion, majorVersion);
+  checkDataProtoVer(messageVersion, msgType) {
+    const messageMajorVersion = VersionUtil.toMajorVersion(messageVersion);
+    const isLower = semver.lt(messageMajorVersion, this.majorDataProtocolVersion);
     if (isLower) {
       if (FeatureFlags.enableRichP2pCommunicationLogging) {
-        logger.error('CANNOT deal with higher data protocol version.' +
-            'Discard the CONSENSUS message.');
+        logger.error(`The given ${msgType} message has unsupported DATA_PROTOCOL_VERSION: ` +
+            `theirs(${messageVersion}) < ours(${this.majorDataProtocolVersion})`);
       }
-      return false;
+      return -1;
     }
-    return true;
-  }
-
-  checkDataProtoVerForTransaction(version) {
-    const majorVersion = VersionUtil.toMajorVersion(version);
-    const isGreater = semver.gt(this.majorDataProtocolVersion, majorVersion);
+    const isGreater = semver.gt(messageMajorVersion, this.majorDataProtocolVersion);
     if (isGreater) {
-      // TODO(minsulee2): Compatible message.
-    }
-    const isLower = semver.lt(this.majorDataProtocolVersion, majorVersion);
-    if (isLower) {
       if (FeatureFlags.enableRichP2pCommunicationLogging) {
-        logger.error('CANNOT deal with higher data protocol ver. Discard the TRANSACTION message.');
+        logger.error('I may be running of the old DATA_PROTOCOL_VERSION ' +
+            `theirs(${messageVersion}) > ours(${this.majorDataProtocolVersion}). ` +
+            'Please check the new release via visiting the URL below:\n' +
+            'https://github.com/ainblockchain/ain-blockchain');
       }
-      return false;
+      return 1;
     }
-    return true;
+    return 0;
   }
 
   setPeerEventHandlers(socket) {
@@ -390,8 +366,12 @@ class P2pServer {
 
         switch (_.get(parsedMessage, 'type')) {
           case MessageTypes.ADDRESS_REQUEST:
-            // TODO(minsulee2): Add compatibility check here after data version up.
-            // this.checkDataProtoVerForAddressRequest(dataProtoVer);
+            const dataVersionCheckForAddress =
+                this.checkDataProtoVer(dataProtoVer, MessageTypes.ADDRESS_REQUEST);
+            if (dataVersionCheckForAddress < 0) {
+              // TODO(minsulee2): need to convert message when updating ADDRESS_REQUEST necessary.
+              // this.convertAddressMessage();
+            }
             const address = _.get(parsedMessage, 'data.body.address');
             if (!address) {
               logger.error(`Providing an address is compulsary when initiating p2p communication.`);
@@ -438,12 +418,16 @@ class P2pServer {
             }
             break;
           case MessageTypes.CONSENSUS:
+            const dataVersionCheckForConsensus =
+                this.checkDataProtoVer(dataProtoVer, MessageTypes.CONSENSUS);
+            if (dataVersionCheckForConsensus !== 0) {
+              logger.error(`[${LOG_HEADER}] The message DATA_PROTOCOL_VERSION(${dataProtoVer}) ` +
+                  'is not compatible. CANNOT proceed the CONSENSUS message.');
+              return;
+            }
             const consensusMessage = _.get(parsedMessage, 'data.message');
             logger.debug(`[${LOG_HEADER}] Receiving a consensus message: ` +
                 `${JSON.stringify(consensusMessage)}`);
-            if (!this.checkDataProtoVerForConsensus(dataProtoVer)) {
-              return;
-            }
             if (this.node.state === BlockchainNodeStates.SERVING) {
               this.consensus.handleConsensusMessage(consensusMessage);
             } else {
@@ -451,6 +435,16 @@ class P2pServer {
             }
             break;
           case MessageTypes.TRANSACTION:
+            const dataVersionCheckForTransaction =
+                this.checkDataProtoVer(dataProtoVer, MessageTypes.TRANSACTION);
+            if (dataVersionCheckForTransaction > 0) {
+              logger.error(`[${LOG_HEADER}] CANNOT deal with higher data protocol ` +
+                  `version(${dataProtoVer}). Discard the TRANSACTION message.`);
+              return;
+            } else if (dataVersionCheckForTransaction < 0) {
+              // TODO(minsulee2): need to convert msg when updating TRANSACTION message necessary.
+              // this.convertTransactionMessage();
+            }
             const tx = _.get(parsedMessage, 'data.transaction');
             logger.debug(`[${LOG_HEADER}] Receiving a transaction: ${JSON.stringify(tx)}`);
             if (this.node.tp.transactionTracker[tx.hash]) {
@@ -463,9 +457,6 @@ class P2pServer {
               return;
             }
             if (Transaction.isBatchTransaction(tx)) {
-              if (!this.checkDataProtoVerForTransaction(dataProtoVer)) {
-                return;
-              }
               const newTxList = [];
               for (const subTx of tx.tx_list) {
                 const createdTx = Transaction.create(subTx.tx_body, subTx.signature);
@@ -490,11 +481,8 @@ class P2pServer {
             }
             break;
           case MessageTypes.CHAIN_SEGMENT_REQUEST:
-            const lastBlock = _.get(parsedMessage, 'data.lastBlock');
-            // NOTE(minsulee2): Communicate with each other
-            // even if the data protocol is incompatible.
-            logger.debug(`[${LOG_HEADER}] Receiving a chain segment request: ` +
-                `${JSON.stringify(lastBlock, null, 2)}`);
+            const lastBlockNumber = _.get(parsedMessage, 'data.lastBlockNumber');
+            logger.debug(`[${LOG_HEADER}] Receiving a chain segment request: ${lastBlockNumber}`);
             if (this.node.bc.chain.length === 0) {
               return;
             }
@@ -506,8 +494,7 @@ class P2pServer {
             // Send a chunk of 20 blocks from your blockchain to the requester.
             // Requester will continue to request blockchain chunks
             // until their blockchain height matches the consensus blockchain height
-            const chainSegment = this.node.bc.requestBlockchainSection(
-                lastBlock ? Block.parse(lastBlock) : null);
+            const chainSegment = this.node.bc.getBlockList(lastBlockNumber + 1);
             if (chainSegment) {
               const catchUpInfo = this.consensus.getCatchUpInfo();
               logger.debug(
@@ -531,8 +518,8 @@ class P2pServer {
             }
             break;
           default:
-            logger.error(`Wrong message type(${parsedMessage.type}) has been specified.`);
-            logger.error('Ignore the message.');
+            logger.error(`[${LOG_HEADER}] Unknown message type(${parsedMessage.type}) has been ` +
+                'specified. Ignore the message.');
             break;
         }
       } catch (err) {
diff --git a/p2p/util.js b/p2p/util.js
index 82a122eff..596078b73 100644
--- a/p2p/util.js
+++ b/p2p/util.js
@@ -10,7 +10,7 @@ const logger = require('../logger')('SERVER_UTIL');
 const {
   CURRENT_PROTOCOL_VERSION,
   DATA_PROTOCOL_VERSION,
-  P2P_MESSAGE_TIMEOUT_MS
+  P2P_MESSAGE_TIMEOUT_MS,
 } = require('../common/constants');
 const ChainUtil = require('../common/chain-util');
 
diff --git a/package.json b/package.json
index b22ec4063..1f8d50f05 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "ain-blockchain",
   "description": "AI Network Blockchain",
-  "version": "0.7.6",
+  "version": "0.7.7",
   "private": true,
   "license": "MIT",
   "author": "dev@ainetwork.ai",
@@ -39,7 +39,6 @@
     "axios": "^0.21.1",
     "bluebird": "^3.5.3",
     "diskusage": "^1.1.3",
-    "elliptic": "^6.4.1",
     "escape-string-regexp": "^2.0.0",
     "express": "^4.17.1",
     "fast-json-stable-stringify": "^2.0.0",
@@ -56,11 +55,9 @@
     "public-ip": "^3.2.0",
     "request": "^2.88.0",
     "rimraf": "^2.6.3",
-    "secp256k1": "^3.7.0",
     "seedrandom": "^2.4.4",
     "semver": "^6.3.0",
     "shuffle-seed": "^1.1.6",
-    "url": "^0.11.0",
     "util": "^0.11.1",
     "uuid": "^3.4.0",
     "valid-url": "^1.0.9",
diff --git a/start_node_incremental_gcp.sh b/start_node_incremental_gcp.sh
index 2df4d62ab..e68ca03f7 100644
--- a/start_node_incremental_gcp.sh
+++ b/start_node_incremental_gcp.sh
@@ -126,6 +126,7 @@ printf "Starting up Node server.."
 nohup node --async-stack-traces client/index.js >/dev/null 2>error_logs.txt &
 
 # 7. Wait until the new node catches up
+SECONDS=0
 loopCount=0
 
 generate_post_data()
@@ -142,7 +143,8 @@ do
     printf "\nconsensusState = ${consensusState}"
     printf "\nlastBlockNumber = ${lastBlockNumber}"
     if [ "$consensusState" == "RUNNING" ]; then
-        printf "\nNode is synced & running!\n\n"
+        printf "\nNode is synced & running!"
+        printf "Time it took to sync in seconds: $SECONDS\n\n"
         break
     fi
     ((loopCount++))
diff --git a/start_servers.sh b/start_servers.sh
index 185c6b55d..e4ec5be10 100755
--- a/start_servers.sh
+++ b/start_servers.sh
@@ -5,7 +5,7 @@ MIN_NUM_VALIDATORS=5 ACCOUNT_INDEX=0 STAKE=100000 CONSOLE_LOG=true ENABLE_DEV_SE
 sleep 10
 MIN_NUM_VALIDATORS=5 ACCOUNT_INDEX=1 STAKE=100000 CONSOLE_LOG=true ENABLE_DEV_SET_CLIENT_API=true ENABLE_TX_SIG_VERIF_WORKAROUND=true ENABLE_GAS_FEE_WORKAROUND=true BLOCKCHAIN_DATA_DIR=~/ain_blockchain_data node ./client/index.js &
 sleep 1
-MIN_NUM_VALIDATORS=5ACCOUNT_INDEX=2 STAKE=100000 CONSOLE_LOG=true ENABLE_DEV_SET_CLIENT_API=true ENABLE_TX_SIG_VERIF_WORKAROUND=true ENABLE_GAS_FEE_WORKAROUND=true BLOCKCHAIN_DATA_DIR=~/ain_blockchain_data node ./client/index.js &
+MIN_NUM_VALIDATORS=5 ACCOUNT_INDEX=2 STAKE=100000 CONSOLE_LOG=true ENABLE_DEV_SET_CLIENT_API=true ENABLE_TX_SIG_VERIF_WORKAROUND=true ENABLE_GAS_FEE_WORKAROUND=true BLOCKCHAIN_DATA_DIR=~/ain_blockchain_data node ./client/index.js &
 sleep 10
 MIN_NUM_VALIDATORS=5 ACCOUNT_INDEX=3 STAKE=100000 CONSOLE_LOG=true ENABLE_DEV_SET_CLIENT_API=true ENABLE_TX_SIG_VERIF_WORKAROUND=true ENABLE_GAS_FEE_WORKAROUND=true BLOCKCHAIN_DATA_DIR=~/ain_blockchain_data node ./client/index.js &
 sleep 10
diff --git a/tx-pool/transaction.js b/tx-pool/transaction.js
index f05c59058..ee4817d29 100644
--- a/tx-pool/transaction.js
+++ b/tx-pool/transaction.js
@@ -170,6 +170,9 @@ class Transaction {
     if (txBody.gas_price !== undefined) {
       sanitized.gas_price = ChainUtil.numberOrZero(txBody.gas_price);
     }
+    if (txBody.billing !== undefined) {
+      sanitized.billing = ChainUtil.stringOrEmpty(txBody.billing);
+    }
     // A devel method for bypassing the transaction verification.
     if (txBody.address !== undefined) {
       sanitized.address = ChainUtil.stringOrEmpty(txBody.address);
@@ -205,6 +208,11 @@ class Transaction {
           `Transaction body has invalid gas price: ${JSON.stringify(txBody, null, 2)}`);
       return false;
     }
+    if (!Transaction.isValidBilling(txBody.billing)) {
+      logger.info(
+          `Transaction body has invalid billing: ${JSON.stringify(txBody, null, 2)}`);
+      return false;
+    }
     return Transaction.isInStandardFormat(txBody);
   }
 
@@ -222,6 +230,10 @@ class Transaction {
     return gasPrice > 0 || ENABLE_GAS_FEE_WORKAROUND && (gasPrice === undefined || gasPrice === 0);
   }
 
+  static isValidBilling(billing) {
+    return billing === undefined || (ChainUtil.isString(billing) && billing.split('|').length === 2);
+  }
+
   static isInStandardFormat(txBody) {
     const sanitized = Transaction.sanitizeTxBody(txBody);
     const isIdentical = _.isEqual(JSON.parse(JSON.stringify(sanitized)), txBody, { strict: true });
diff --git a/unittest/blockchain.test.js b/unittest/blockchain.test.js
index daf02db67..f447d9b92 100644
--- a/unittest/blockchain.test.js
+++ b/unittest/blockchain.test.js
@@ -103,7 +103,7 @@ describe('Blockchain', () => {
 
     it('can sync on startup', () => {
       while (!node1.bc.lastBlock() || !node2.bc.lastBlock() || node1.bc.lastBlock().hash !== node2.bc.lastBlock().hash) {
-        const blockSection = node1.bc.requestBlockchainSection(node2.bc.lastBlock());
+        const blockSection = node1.bc.getBlockList(node2.bc.lastBlock().number + 1);
         if (blockSection) {
           node2.mergeChainSegment(blockSection);
         }
@@ -112,9 +112,9 @@ describe('Blockchain', () => {
     });
 
     it('can be queried by index', () => {
-      assert.deepEqual(JSON.stringify(node1.bc.getChainSection(10, 30)),
+      assert.deepEqual(JSON.stringify(node1.bc.getBlockList(10, 30)),
           JSON.stringify(blocks.slice(9, 29)));
-      assert.deepEqual(JSON.stringify(node1.bc.getChainSection(980, 1000)),
+      assert.deepEqual(JSON.stringify(node1.bc.getBlockList(980, 1000)),
           JSON.stringify(blocks.slice(979, 999)));
     });
 
diff --git a/unittest/chain-util.test.js b/unittest/chain-util.test.js
index 83d3472cd..c2372a492 100644
--- a/unittest/chain-util.test.js
+++ b/unittest/chain-util.test.js
@@ -1131,4 +1131,95 @@ describe("ChainUtil", () => {
       assert.deepEqual(ChainUtil.getTotalGasCost(undefined, 1), 0);
     })
   })
+
+  describe('getDependentAppNameFromRef', () => {
+    it("when abnormal input", () => {
+      assert.deepEqual(ChainUtil.getDependentAppNameFromRef(), null);
+      assert.deepEqual(ChainUtil.getDependentAppNameFromRef(null), null);
+      assert.deepEqual(ChainUtil.getDependentAppNameFromRef(undefined), null);
+      assert.deepEqual(ChainUtil.getDependentAppNameFromRef(''), null);
+      assert.deepEqual(ChainUtil.getDependentAppNameFromRef('/'), null);
+    });
+
+    it("when normal input (app-dependent service path)", () => {
+      assert.deepEqual(ChainUtil.getDependentAppNameFromRef('/manage_app/app_a'), 'app_a');
+      assert.deepEqual(ChainUtil.getDependentAppNameFromRef('/payments/app_a'), 'app_a');
+      assert.deepEqual(ChainUtil.getDependentAppNameFromRef('/staking/app_a'), 'app_a');
+      assert.deepEqual(ChainUtil.getDependentAppNameFromRef('/staking/app_a/some/nested/path'), 'app_a');
+    });
+    
+    it("when normal input (app-independent service path)", () => {
+      assert.deepEqual(ChainUtil.getDependentAppNameFromRef('/accounts/0xabcd/value'), null);
+      assert.deepEqual(ChainUtil.getDependentAppNameFromRef('/service_accounts/staking'), null);
+      assert.deepEqual(ChainUtil.getDependentAppNameFromRef('/gas_fee/gas_fee'), null);
+      assert.deepEqual(ChainUtil.getDependentAppNameFromRef('/escrow/source/target/id/key/value'), null);
+      assert.deepEqual(ChainUtil.getDependentAppNameFromRef('/sharding/config'), null);
+      assert.deepEqual(ChainUtil.getDependentAppNameFromRef('/transfer'), null);
+      assert.deepEqual(ChainUtil.getDependentAppNameFromRef('/transfer/from/to/key/value'), null);
+    });
+  })
+
+  describe('getServiceDependentAppNameList', () => { 
+    it("when abnormal input", () => {
+      assert.deepEqual(ChainUtil.getServiceDependentAppNameList(), []);
+      assert.deepEqual(ChainUtil.getServiceDependentAppNameList(null), []);
+      assert.deepEqual(ChainUtil.getServiceDependentAppNameList(undefined), []);
+      assert.deepEqual(ChainUtil.getServiceDependentAppNameList({}), []);
+    });
+
+    it("when normal input", () => {
+      assert.deepEqual(ChainUtil.getServiceDependentAppNameList({
+        ref: '/'
+      }), []);
+      assert.deepEqual(ChainUtil.getServiceDependentAppNameList({
+        ref: '/transfer/from/to/key/value'
+      }), []);
+      assert.deepEqual(ChainUtil.getServiceDependentAppNameList({
+        ref: '/manage_app/app_a/create/key'
+      }), ['app_a']);
+      assert.deepEqual(ChainUtil.getServiceDependentAppNameList({
+        op_list: [
+          {
+            ref: '/'
+          }
+        ]
+      }), []);
+      assert.deepEqual(ChainUtil.getServiceDependentAppNameList({
+        op_list: [
+          {
+            ref: '/transfer/from/to/key/value'
+          },
+          {
+            ref: '/manage_app/app_a/create/key'
+          }
+        ]
+      }), ['app_a']);
+      assert.deepEqual(ChainUtil.getServiceDependentAppNameList({
+        op_list: [
+          {
+            ref: '/transfer/from/to/key/value'
+          },
+          {
+            ref: '/manage_app/app_a/create/key'
+          },
+          {
+            ref: '/payments/app_a/user/id/pay/key'
+          }
+        ]
+      }), ['app_a']);
+      assert.deepEqual(ChainUtil.getServiceDependentAppNameList({
+        op_list: [
+          {
+            ref: '/transfer/from/to/key/value'
+          },
+          {
+            ref: '/manage_app/app_a/create/key'
+          },
+          {
+            ref: '/payments/app_b/user/id/pay/key'
+          }
+        ]
+      }), ['app_a', 'app_b']);
+    });
+  })
 })
\ No newline at end of file
diff --git a/unittest/db.test.js b/unittest/db.test.js
index 1097bcb4b..9b3e4e3c7 100644
--- a/unittest/db.test.js
+++ b/unittest/db.test.js
@@ -1451,7 +1451,20 @@ describe("DB operations", () => {
                 "write_rule": true,
               }
             }
-          }};
+          },
+          "deeper": {
+            ".owner": {  // deeper owner
+              "owners": {
+                "*": {
+                  "branch_owner": true,
+                  "write_function": true,
+                  "write_owner": true,
+                  "write_rule": true,
+                }
+              }
+            }
+          }
+        };
         assert.deepEqual(node.db.setOwner(
             "/test/test_owner/some/path", ownerTree,
             { addr: '0x09A0d53FDf1c36A131938eb379b98910e55EEfe1' }), {
@@ -1804,6 +1817,18 @@ describe("DB operations", () => {
                     "write_rule": true,
                   }
                 }
+              },
+              "deeper": {
+                ".owner": {  // deeper owner
+                  "owners": {
+                    "*": {
+                      "branch_owner": true,
+                      "write_function": true,
+                      "write_owner": true,
+                      "write_rule": true,
+                    }
+                  }
+                }
               }
             }
           }
@@ -1873,6 +1898,18 @@ describe("DB operations", () => {
                     "write_rule": true,
                   }
                 }
+              },
+              "deeper": {
+                ".owner": {  // deeper owner
+                  "owners": {
+                    "*": {
+                      "branch_owner": true,
+                      "write_function": true,
+                      "write_owner": true,
+                      "write_rule": true,
+                    }
+                  }
+                }
               }
             });
       })
@@ -3041,10 +3078,10 @@ describe("DB sharding config", () => {
             ".owner": {
               "owners": {
                 "*": {
-                  "branch_owner": false,
-                  "write_function": false,
-                  "write_owner": false,
-                  "write_rule": false,
+                  "branch_owner": true,
+                  "write_function": true,
+                  "write_owner": true,
+                  "write_rule": true,
                 },
                 "0x09A0d53FDf1c36A131938eb379b98910e55EEfe1": {
                   "branch_owner": true,
@@ -3053,6 +3090,18 @@ describe("DB sharding config", () => {
                   "write_rule": true,
                 }
               }
+            },
+            "deeper": {
+              ".owner": {  // deeper owner
+                "owners": {
+                  "*": {
+                    "branch_owner": true,
+                    "write_function": true,
+                    "write_owner": true,
+                    "write_rule": true,
+                  },
+                }
+              }
             }
           }
         }
@@ -3544,10 +3593,10 @@ describe("DB sharding config", () => {
       ".owner": {
         "owners": {
           "*": {
-            "branch_owner": false,
-            "write_function": false,
-            "write_owner": false,
-            "write_rule": false,
+            "branch_owner": true,
+            "write_function": true,
+            "write_owner": true,
+            "write_rule": true,
           },
           "0x09A0d53FDf1c36A131938eb379b98910e55EEfe1": {
             "branch_owner": true,
@@ -3556,18 +3605,49 @@ describe("DB sharding config", () => {
             "write_rule": true,
           }
         }
+      },
+      "deeper": {
+        ".owner": {  // deeper owner
+          "owners": {
+            "*": {
+              "branch_owner": true,
+              "write_function": true,
+              "write_owner": true,
+              "write_rule": true,
+            },
+          }
+        }
+      }
+    };
+    const ownerChange = {
+      ".owner": {
+        "owners": {
+          "0x09A0d53FDf1c36A131938eb379b98910e55EEfe1": null
+        }
       }
     };
     const newOwner = {
       ".owner": {
         "owners": {
           "*": {
-            "branch_owner": false,
-            "write_function": false,
-            "write_owner": false,
-            "write_rule": false,
+            "branch_owner": true,
+            "write_function": true,
+            "write_owner": true,
+            "write_rule": true,
           },
         }
+      },
+      "deeper": {
+        ".owner": {  // deeper owner
+          "owners": {
+            "*": {
+              "branch_owner": true,
+              "write_function": true,
+              "write_owner": true,
+              "write_rule": true,
+            },
+          }
+        }
       }
     };
 
@@ -3588,7 +3668,7 @@ describe("DB sharding config", () => {
 
     it("setOwner with isGlobal = false", () => {
       expect(node.db.setOwner(
-          "test/test_sharding/some/path/to", newOwner,
+          "test/test_sharding/some/path/to", ownerChange,
           { addr: '0x09A0d53FDf1c36A131938eb379b98910e55EEfe1' }).code)
               .to.equal(0);
       assert.deepEqual(node.db.getOwner("test/test_sharding/some/path/to"), newOwner);
@@ -3596,7 +3676,7 @@ describe("DB sharding config", () => {
 
     it("setOwner with isGlobal = true", () => {
       expect(node.db.setOwner(
-          "apps/afan/test/test_sharding/some/path/to", newOwner,
+          "apps/afan/test/test_sharding/some/path/to", ownerChange,
           { addr: '0x09A0d53FDf1c36A131938eb379b98910e55EEfe1' }, true).code)
               .to.equal(0);
       assert.deepEqual(
@@ -3605,7 +3685,7 @@ describe("DB sharding config", () => {
 
     it("setOwner with isGlobal = true and non-existing path", () => {
       expect(node.db.setOwner(
-          "some/non-existing/path", newOwner,
+          "some/non-existing/path", ownerChange,
           { addr: '0x09A0d53FDf1c36A131938eb379b98910e55EEfe1' }, true).code).to.equal(0);
     })
 
@@ -3618,10 +3698,10 @@ describe("DB sharding config", () => {
           "config": {
             "owners": {
               "*": {
-                "branch_owner": false,
-                "write_function": false,
-                "write_owner": false,
-                "write_rule": false,
+                "branch_owner": true,
+                "write_function": true,
+                "write_owner": true,
+                "write_rule": true,
               },
               "0x09A0d53FDf1c36A131938eb379b98910e55EEfe1": {
                 "branch_owner": true,
@@ -3645,10 +3725,10 @@ describe("DB sharding config", () => {
           "config": {
             "owners": {
               "*": {
-                "branch_owner": false,
-                "write_function": false,
-                "write_owner": false,
-                "write_rule": false,
+                "branch_owner": true,
+                "write_function": true,
+                "write_owner": true,
+                "write_rule": true,
               },
               "0x09A0d53FDf1c36A131938eb379b98910e55EEfe1": {
                 "branch_owner": true,
diff --git a/unittest/p2p.test.js b/unittest/p2p.test.js
index 97f46d085..3c63d3979 100644
--- a/unittest/p2p.test.js
+++ b/unittest/p2p.test.js
@@ -1,5 +1,4 @@
 const chai = require('chai');
-const url = require('url');
 
 const BlockchainNode = require('../node');
 const VersionUtil = require('../common/version-util');
@@ -270,31 +269,26 @@ describe("p2p", () => {
 
     describe("getNetworkStatus", () => {
       it("shows initial values of connection status", () => {
+        const extIp = p2pClient.server.getExternalIp();
+        const url = new URL(`ws://${extIp}:${P2P_PORT}`);
+        const p2pUrl = url.toString();
+        url.protocol = 'http:';
+        url.port = PORT;
+        const clientApiUrl = url.toString();
+        url.pathname = 'json-rpc';
+        const jsonRpcUrl = url.toString();
         const actual = {
-          ip: p2pClient.server.getExternalIp(),
+          ip: extIp,
           p2p: {
-            url: url.format({
-              protocol: 'ws',
-              hostname: p2pClient.server.getExternalIp(),
-              port: P2P_PORT
-            }),
+            url: p2pUrl,
             port: P2P_PORT,
           },
           clientApi: {
-            url: url.format({
-              protocol: 'http',
-              hostname: p2pClient.server.getExternalIp(),
-              port: PORT
-            }),
+            url: clientApiUrl,
             port: PORT,
           },
           jsonRpc: {
-            url: url.format({
-              protocol: 'http',
-              hostname: p2pClient.server.getExternalIp(),
-              port: PORT,
-              pathname: '/json-rpc',
-            }),
+            url: jsonRpcUrl,
             port: PORT,
           },
           connectionStatus: p2pClient.getConnectionStatus()
diff --git a/unittest/rule-util.test.js b/unittest/rule-util.test.js
index 1f8313483..0c737e99d 100644
--- a/unittest/rule-util.test.js
+++ b/unittest/rule-util.test.js
@@ -352,4 +352,38 @@ describe("RuleUtil", () => {
       expect(util.isCksumAddr('0xCAcD898dBaEdBD9037aCd25b82417587E972838d')).to.equal(true);
     })
   })
+
+  describe("isServAcntName", () => {
+    it("when invalid-address input", () => {
+      expect(util.isServAcntName(0)).to.equal(false);
+      expect(util.isServAcntName(10)).to.equal(false);
+      expect(util.isServAcntName(null)).to.equal(false);
+      expect(util.isServAcntName(undefined)).to.equal(false);
+      expect(util.isServAcntName(Infinity)).to.equal(false);
+      expect(util.isServAcntName(NaN)).to.equal(false);
+      expect(util.isServAcntName({})).to.equal(false);
+      expect(util.isServAcntName({a: 'a'})).to.equal(false);
+      expect(util.isServAcntName('')).to.equal(false);
+      expect(util.isServAcntName('abc')).to.equal(false);
+      expect(util.isServAcntName('0')).to.equal(false);
+      expect(util.isServAcntName([])).to.equal(false);
+      expect(util.isServAcntName([10])).to.equal(false);
+      expect(util.isServAcntName([10, 'abc'])).to.equal(false);
+      expect(util.isServAcntName('staking')).to.equal(false);
+      expect(util.isServAcntName('staking|consensus')).to.equal(false);
+      expect(util.isServAcntName(
+          'invalid_service_type|consensus|0x09A0d53FDf1c36A131938eb379b98910e55EEfe1|0'))
+          .to.equal(false);  // invalid service account service type
+      expect(util.isServAcntName(
+          'staking|0invalid_service_name|0x09A0d53FDf1c36A131938eb379b98910e55EEfe1|0'))
+          .to.equal(false);  // invalid service account service name
+    })
+
+    it("when valid-address input", () => {
+      expect(util.isServAcntName(
+          'staking|consensus|0x09A0d53FDf1c36A131938eb379b98910e55EEfe1')).to.equal(true);
+      expect(util.isServAcntName(
+          'staking|consensus|0x09A0d53FDf1c36A131938eb379b98910e55EEfe1|0')).to.equal(true);
+    })
+  })
 })
\ No newline at end of file
diff --git a/unittest/state-util.test.js b/unittest/state-util.test.js
index 9bccdc91a..99f4b5907 100644
--- a/unittest/state-util.test.js
+++ b/unittest/state-util.test.js
@@ -3,6 +3,7 @@ const {
   isWritablePathWithSharding,
   hasReservedChar,
   hasAllowedPattern,
+  isValidServiceName,
   isValidStateLabel,
   isValidPathForStates,
   isValidJsObjectForStates,
@@ -13,6 +14,7 @@ const {
   isValidOwnerConfig,
   isValidOwnerTree,
   applyFunctionChange,
+  applyOwnerChange,
   setStateTreeVersion,
   renameStateTreeVersion,
   deleteStateTree,
@@ -23,6 +25,9 @@ const {
   updateProofHashForAllRootPaths,
   verifyProofHashForStateTree
 } = require('../db/state-util');
+const {
+  STATE_LABEL_LENGTH_LIMIT,
+} = require('../common/constants');
 const StateNode = require('../db/state-node');
 const chai = require('chai');
 const expect = chai.expect;
@@ -295,6 +300,69 @@ describe("state-util", () => {
     })
   })
 
+  describe("isValidServiceName", () => {
+    it("when non-string input", () => {
+      expect(isValidServiceName(null)).to.equal(false);
+      expect(isValidServiceName(undefined)).to.equal(false);
+      expect(isValidServiceName(true)).to.equal(false);
+      expect(isValidServiceName(false)).to.equal(false);
+      expect(isValidServiceName(0)).to.equal(false);
+      expect(isValidServiceName([])).to.equal(false);
+      expect(isValidServiceName({})).to.equal(false);
+    })
+
+    it("when string input returning false", () => {
+      expect(isValidServiceName('')).to.equal(false);
+      expect(isValidServiceName('.')).to.equal(false);
+      expect(isValidServiceName('.a')).to.equal(false);
+      expect(isValidServiceName('$')).to.equal(false);
+      expect(isValidServiceName('$a')).to.equal(false);
+      expect(isValidServiceName('*')).to.equal(false);
+      expect(isValidServiceName('~')).to.equal(false);
+      expect(isValidServiceName('!')).to.equal(false);
+      expect(isValidServiceName('@')).to.equal(false);
+      expect(isValidServiceName('%')).to.equal(false);
+      expect(isValidServiceName('^')).to.equal(false);
+      expect(isValidServiceName('&')).to.equal(false);
+      expect(isValidServiceName('-')).to.equal(false);
+      expect(isValidServiceName('=')).to.equal(false);
+      expect(isValidServiceName('+')).to.equal(false);
+      expect(isValidServiceName('|')).to.equal(false);
+      expect(isValidServiceName(';')).to.equal(false);
+      expect(isValidServiceName(',')).to.equal(false);
+      expect(isValidServiceName('?')).to.equal(false);
+      expect(isValidServiceName('/')).to.equal(false);
+      expect(isValidServiceName("'")).to.equal(false);
+      expect(isValidServiceName('"')).to.equal(false);
+      expect(isValidServiceName('`')).to.equal(false);
+      expect(isValidServiceName('\x00')).to.equal(false);
+      expect(isValidServiceName('\x7F')).to.equal(false);
+    })
+
+    it("when string input without alphabetic prefix returning false", () => {
+      expect(isValidServiceName('0')).to.equal(false);
+      expect(isValidServiceName('0a')).to.equal(false);
+      expect(isValidServiceName('0a0')).to.equal(false);
+      expect(isValidServiceName('0_')).to.equal(false);
+      expect(isValidServiceName('0_0')).to.equal(false);
+    })
+
+    it("when string input returning true", () => {
+      expect(isValidServiceName('a')).to.equal(true);
+      expect(isValidServiceName('aa')).to.equal(true);
+      expect(isValidServiceName('a_')).to.equal(true);
+      expect(isValidServiceName('a0')).to.equal(true);
+      expect(isValidServiceName('a0a')).to.equal(true);
+      expect(isValidServiceName('_')).to.equal(true);
+      expect(isValidServiceName('_0')).to.equal(true);
+      expect(isValidServiceName('_0_')).to.equal(true);
+      expect(isValidServiceName('consensus')).to.equal(true);
+      expect(isValidServiceName('afan')).to.equal(true);
+      expect(isValidServiceName('collaborative_ai')).to.equal(true);
+      expect(isValidServiceName('_a_dapp')).to.equal(true);
+    })
+  })
+
   describe("isValidStateLabel", () => {
     it("when non-string input", () => {
       expect(isValidStateLabel(null)).to.equal(false);
@@ -318,6 +386,7 @@ describe("state-util", () => {
 
     it("when string input returning true", () => {
       expect(isValidStateLabel('a')).to.equal(true);
+      expect(isValidStateLabel('0')).to.equal(true);
       expect(isValidStateLabel('.a')).to.equal(true);
       expect(isValidStateLabel('$a')).to.equal(true);
       expect(isValidStateLabel('*')).to.equal(true);
@@ -336,6 +405,13 @@ describe("state-util", () => {
       expect(isValidStateLabel(',')).to.equal(true);
       expect(isValidStateLabel('?')).to.equal(true);
     })
+
+    it("when long string input", () => {
+      const labelLong = 'a'.repeat(STATE_LABEL_LENGTH_LIMIT);
+      expect(isValidStateLabel(labelLong)).to.equal(true);
+      const labelTooLong = 'a'.repeat(STATE_LABEL_LENGTH_LIMIT + 1);
+      expect(isValidStateLabel(labelTooLong)).to.equal(false);
+    })
   })
 
   describe("isValidPathForStates", () => {
@@ -381,6 +457,19 @@ describe("state-util", () => {
       assert.deepEqual(isValidPathForStates(['a', '$b']), {isValid: true, invalidPath: ''});
       assert.deepEqual(isValidPathForStates(['a', '*']), {isValid: true, invalidPath: ''});
     })
+
+    it("when input with long labels", () => {
+      const labelLong = 'a'.repeat(STATE_LABEL_LENGTH_LIMIT);
+      const labelTooLong = 'a'.repeat(STATE_LABEL_LENGTH_LIMIT + 1);
+      assert.deepEqual(
+          isValidPathForStates([labelLong, labelLong]), {isValid: true, invalidPath: ''});
+      assert.deepEqual(
+          isValidPathForStates([labelTooLong, labelLong]),
+          {isValid: false, invalidPath: `/${labelTooLong}`});
+      assert.deepEqual(
+          isValidPathForStates([labelLong, labelTooLong]),
+          {isValid: false, invalidPath: `/${labelLong}/${labelTooLong}`});
+    })
   })
 
   describe("isValidJsObjectForStates", () => {
@@ -527,6 +616,29 @@ describe("state-util", () => {
           }
       }), {isValid: true, invalidPath: ''});
     })
+
+    it("when input with long labels", () => {
+      const textLong = 'a'.repeat(STATE_LABEL_LENGTH_LIMIT);
+      const textTooLong = 'a'.repeat(STATE_LABEL_LENGTH_LIMIT + 1);
+      assert.deepEqual(
+        isValidJsObjectForStates({
+          [textLong]: {
+            [textLong]: textTooLong
+          }
+      }), {isValid: true, invalidPath: ''});
+      assert.deepEqual(
+        isValidJsObjectForStates({
+          [textTooLong]: {
+            [textLong]: textTooLong
+          }
+      }), {isValid: false, invalidPath: `/${textTooLong}`});
+      assert.deepEqual(
+        isValidJsObjectForStates({
+          [textLong]: {
+            [textTooLong]: textTooLong
+          }
+      }), {isValid: false, invalidPath: `/${textLong}/${textTooLong}`});
+    })
   })
 
   describe("isValidRuleConfig", () => {
@@ -1136,7 +1248,20 @@ describe("state-util", () => {
               "write_function": false,
               "write_owner": false,
               "write_rule": false,
-            }
+            },
+            '0x09A0d53FDf1c36A131938eb379b98910e55EEfe1': {
+              "branch_owner": true,
+              "write_function": false,
+              "write_owner": false,
+              "write_rule": false,
+            },
+            'fid:_createApp': {
+              "branch_owner": true,
+              "write_function": false,
+              "write_owner": false,
+              "write_rule": false,
+            },
+            '0x08Aed7AF9354435c38d52143EE50ac839D20696b': null
           }
         }
       }), {isValid: true, invalidPath: ''});
@@ -1149,7 +1274,20 @@ describe("state-util", () => {
                 "write_function": false,
                 "write_owner": false,
                 "write_rule": false,
-              }
+              },
+              '0x09A0d53FDf1c36A131938eb379b98910e55EEfe1': {
+                "branch_owner": true,
+                "write_function": false,
+                "write_owner": false,
+                "write_rule": false,
+              },
+              'fid:_createApp': {
+                "branch_owner": true,
+                "write_function": false,
+                "write_owner": false,
+                "write_rule": false,
+              },
+              '0x08Aed7AF9354435c38d52143EE50ac839D20696b': null
             }
           }
         },
@@ -1161,7 +1299,20 @@ describe("state-util", () => {
                 "write_function": false,
                 "write_owner": false,
                 "write_rule": false,
-              }
+              },
+              '0x09A0d53FDf1c36A131938eb379b98910e55EEfe1': {
+                "branch_owner": true,
+                "write_function": false,
+                "write_owner": false,
+                "write_rule": false,
+              },
+              'fid:_createApp': {
+                "branch_owner": true,
+                "write_function": false,
+                "write_owner": false,
+                "write_rule": false,
+              },
+              '0x08Aed7AF9354435c38d52143EE50ac839D20696b': null
             }
           }
         }
@@ -1173,17 +1324,25 @@ describe("state-util", () => {
     const curFunction = {
       ".function": {
         "0x111": {
-          "function_type": "NATIVE",
+          "function_type": "REST",
           "function_id": "0x111"
         },
         "0x222": {
-          "function_type": "NATIVE",
+          "function_type": "REST",
           "function_id": "0x222"
         },
         "0x333": {
-          "function_type": "NATIVE",
+          "function_type": "REST",
           "function_id": "0x333"
         }
+      },
+      "deeper": {
+        ".function": {  // deeper function
+          "0x999": {
+            "function_type": "REST",
+            "function_id": "0x999"
+          }
+        }
       }
     };
 
@@ -1198,9 +1357,9 @@ describe("state-util", () => {
         },
         "deeper": {
           ".function": {  // deeper function
-            "0x999": {
+            "0x888": {
               "function_type": "REST",
-              "function_id": "0x999"
+              "function_id": "0x888"
             }
           }
         }
@@ -1214,9 +1373,9 @@ describe("state-util", () => {
         },
         "deeper": {
           ".function": {
-            "0x999": {
+            "0x888": {
               "function_type": "REST",
-              "function_id": "0x999"
+              "function_id": "0x888"
             }
           }
         }
@@ -1229,7 +1388,8 @@ describe("state-util", () => {
           "0x111": null,  // delete
           "0x222": {  // modify
             "function_type": "REST",
-            "function_id": "0x222"
+            "function_id": "0x222",
+            "service_name": "https://ainetwork.ai",
           },
           "0x444": {  // add
             "function_type": "REST",
@@ -1240,27 +1400,36 @@ describe("state-util", () => {
         ".function": {
           "0x222": {  // modified
             "function_type": "REST",
-            "function_id": "0x222"
+            "function_id": "0x222",
+            "service_name": "https://ainetwork.ai",
           },
           "0x333": {  // untouched
-            "function_type": "NATIVE",
+            "function_type": "REST",
             "function_id": "0x333"
           },
           "0x444": {  // added
             "function_type": "REST",
             "function_id": "0x444"
           }
+        },
+        "deeper": {
+          ".function": {  // deeper function
+            "0x999": {
+              "function_type": "REST",
+              "function_id": "0x999"
+            }
+          }
         }
       });
     });
 
-    it("add / delete / modify existing function with deeper function", () => {
+    it("replace existing function with deeper function", () => {
       assert.deepEqual(applyFunctionChange(curFunction, {
         ".function": {
-          "0x111": null,  // delete
           "0x222": {  // modify
             "function_type": "REST",
-            "function_id": "0x222"
+            "function_id": "0x222",
+            "service_name": "https://ainetwork.ai",
           },
           "0x444": {  // add
             "function_type": "REST",
@@ -1269,26 +1438,31 @@ describe("state-util", () => {
         },
         "deeper": {
           ".function": {  // deeper function
-            "0x999": {
+            "0x888": {
               "function_type": "REST",
-              "function_id": "0x999"
+              "function_id": "0x888"
             }
           }
         }
       }), {
-        ".function": {  // deeper function has no effect
-          "0x222": {  // modified
+        ".function": {  // replaced
+          "0x222": {
             "function_type": "REST",
-            "function_id": "0x222"
-          },
-          "0x333": {  // untouched
-            "function_type": "NATIVE",
-            "function_id": "0x333"
+            "function_id": "0x222",
+            "service_name": "https://ainetwork.ai",
           },
-          "0x444": {  // added
+          "0x444": {
             "function_type": "REST",
             "function_id": "0x444"
           }
+        },
+        "deeper": {  // replaced
+          ".function": {
+            "0x888": {
+              "function_type": "REST",
+              "function_id": "0x888"
+            }
+          }
         }
       });
     });
@@ -1298,6 +1472,218 @@ describe("state-util", () => {
     });
   });
 
+  describe("applyOwnerChange()", () => {
+    const curOwner = {
+      ".owner": {
+        "owners": {
+          "*": {
+            "branch_owner": true,
+            "write_function": true,
+            "write_owner": true,
+            "write_rule": true,
+          },
+          "aaaa": {
+            "branch_owner": true,
+            "write_function": true,
+            "write_owner": true,
+            "write_rule": true,
+          },
+          "bbbb": {
+            "branch_owner": true,
+            "write_function": true,
+            "write_owner": true,
+            "write_rule": true,
+          }
+        }
+      },
+      "deeper": {
+        ".owner": {  // deeper owner
+          "owners": {
+            "*": {
+              "branch_owner": true,
+              "write_function": true,
+              "write_owner": true,
+              "write_rule": true,
+            },
+          }
+        }
+      }
+    };
+
+    it("add / delete / modify non-existing owner", () => {
+      assert.deepEqual(applyOwnerChange(null, {
+        ".owner": {  // owner
+          "owners": {
+            "*": {
+              "branch_owner": true,
+              "write_function": true,
+              "write_owner": true,
+              "write_rule": true,
+            },
+          }
+        },
+        "deeper": {
+          ".owner": {  // deeper owner
+            "owners": {
+              "*": {
+                "branch_owner": true,
+                "write_function": true,
+                "write_owner": true,
+                "write_rule": true,
+              },
+            }
+          }
+        }
+      }), {  // the same as the given owner change.
+        ".owner": {  // owner
+          "owners": {
+            "*": {
+              "branch_owner": true,
+              "write_function": true,
+              "write_owner": true,
+              "write_rule": true,
+            },
+          }
+        },
+        "deeper": {
+          ".owner": {  // deeper owner
+            "owners": {
+              "*": {
+                "branch_owner": true,
+                "write_function": true,
+                "write_owner": true,
+                "write_rule": true,
+              },
+            }
+          }
+        }
+      });
+    });
+
+    it("add / delete / modify existing owner", () => {
+      assert.deepEqual(applyOwnerChange(curOwner, {
+        ".owner": {
+          "owners": {
+            "*": {  // modify
+              "branch_owner": true,
+              "write_function": false,
+              "write_owner": false,
+              "write_rule": false,
+            },
+            "aaaa": null,  // delete
+            "cccc": {  // add
+              "branch_owner": true,
+              "write_function": true,
+              "write_owner": true,
+              "write_rule": true,
+            }
+          }
+        }
+      }), {
+        ".owner": {
+          "owners": {
+            "*": {  // modified
+              "branch_owner": true,
+              "write_function": false,
+              "write_owner": false,
+              "write_rule": false,
+            },
+            "bbbb": {  // untouched
+              "branch_owner": true,
+              "write_function": true,
+              "write_owner": true,
+              "write_rule": true,
+            },
+            "cccc": {  // added
+              "branch_owner": true,
+              "write_function": true,
+              "write_owner": true,
+              "write_rule": true,
+            }
+          }
+        },
+        "deeper": {
+          ".owner": {  // deeper owner
+            "owners": {
+              "*": {
+                "branch_owner": true,
+                "write_function": true,
+                "write_owner": true,
+                "write_rule": true,
+              },
+            }
+          }
+        }
+      });
+    });
+
+    it("replace existing owner with deeper owner", () => {
+      assert.deepEqual(applyOwnerChange(curOwner, {
+        ".owner": {
+          "owners": {
+            "*": {  // modify
+              "branch_owner": true,
+              "write_function": false,
+              "write_owner": false,
+              "write_rule": false,
+            },
+            "cccc": {  // add
+              "branch_owner": true,
+              "write_function": true,
+              "write_owner": true,
+              "write_rule": true,
+            }
+          }
+        },
+        "deeper": {
+          ".owner": {  // deeper owner
+            "owners": {
+              "CCCC": {
+                "branch_owner": true,
+                "write_function": true,
+                "write_owner": true,
+                "write_rule": true,
+              }
+            }
+          }
+        }
+      }), {
+        ".owner": {  // replaced
+          "owners": {
+            "*": {  // modify
+              "branch_owner": true,
+              "write_function": false,
+              "write_owner": false,
+              "write_rule": false,
+            },
+            "cccc": {  // add
+              "branch_owner": true,
+              "write_function": true,
+              "write_owner": true,
+              "write_rule": true,
+            }
+          }
+        },
+        "deeper": {  // replaced
+          ".owner": {
+            "owners": {
+              "CCCC": {
+                "branch_owner": true,
+                "write_function": true,
+                "write_owner": true,
+                "write_rule": true,
+              }
+            }
+          }
+        }
+      });
+    });
+
+    it("with null owner change", () => {
+      assert.deepEqual(applyOwnerChange(curOwner, null), null);
+    });
+  });
+
   describe("setStateTreeVersion", () => {
     it("leaf node", () => {
       const ver1 = 'ver1';
diff --git a/unittest/transaction.test.js b/unittest/transaction.test.js
index b5c849ee0..d3a8d2ed1 100644
--- a/unittest/transaction.test.js
+++ b/unittest/transaction.test.js
@@ -19,6 +19,8 @@ describe('Transaction', () => {
   let txCustomAddress;
   let txBodyParentHash;
   let txParentHash;
+  let txBodyBilling;
+  let txBilling;
   let txBodyForNode;
   let txForNode;
 
@@ -66,6 +68,19 @@ describe('Transaction', () => {
     };
     txParentHash = Transaction.fromTxBody(txBodyParentHash, node.account.private_key);
 
+    txBodyBilling = {
+      operation: {
+        type: 'SET_VALUE',
+        ref: '/apps/app_a/path',
+        value: 'val',
+      },
+      timestampe: 1568798344000,
+      nonce: 10,
+      gas_price: 1,
+      billing: 'app_a|0'
+    };
+    txBilling = Transaction.fromTxBody(txBodyBilling, node.account.private_key);
+
     txBodyForNode = {
       operation: {
         type: 'SET_VALUE',
@@ -151,6 +166,22 @@ describe('Transaction', () => {
       let tx3 = Transaction.fromTxBody(txBody, node.account.private_key);
       assert.deepEqual(tx3, null);
     });
+
+    it('succeed with absent billing', () => {
+      delete txBody.billing;
+      const tx2 = Transaction.fromTxBody(txBody, node.account.private_key);
+      expect(tx2).to.not.equal(null);
+    });
+
+    it('fail with invalid billing', () => {
+      txBody.billing = 'app_a';
+      const tx2 = Transaction.fromTxBody(txBody, node.account.private_key);
+      assert.deepEqual(tx2, null);
+
+      txBody.billing = 'app_a|0|1';
+      const tx3 = Transaction.fromTxBody(txBody, node.account.private_key);
+      assert.deepEqual(tx3, null);
+    });
   });
 
   describe('isExecutable / toExecutable / toJsObject', () => {
@@ -277,5 +308,10 @@ describe('Transaction', () => {
       txParentHash.tx_body.parent_tx_hash = '';
       expect(Transaction.verifyTransaction(txParentHash)).to.equal(false);
     });
+
+    it('fail to verify an invalid transaction with altered billing', () => {
+      txParentHash.tx_body.billing = 'app_b|0';
+      expect(Transaction.verifyTransaction(txParentHash)).to.equal(false);
+    });
   });
 });
diff --git a/yarn.lock b/yarn.lock
index 99ae718eb..f0e615c8b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2202,9 +2202,9 @@ getpass@^0.1.1:
     assert-plus "^1.0.0"
 
 glob-parent@^5.0.0, glob-parent@~5.1.0:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
-  integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
+  integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
   dependencies:
     is-glob "^4.0.1"
 
@@ -3276,9 +3276,9 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
   integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
 
 normalize-url@^4.1.0:
-  version "4.5.0"
-  resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129"
-  integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==
+  version "4.5.1"
+  resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a"
+  integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==
 
 ntp-client@^0.5.3:
   version "0.5.3"
@@ -3707,11 +3707,6 @@ pumpify@^2.0.1:
     inherits "^2.0.3"
     pump "^3.0.0"
 
-punycode@1.3.2:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
-  integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=
-
 punycode@^2.1.0, punycode@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
@@ -3737,11 +3732,6 @@ qs@~6.5.2:
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
   integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
 
-querystring@0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
-  integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
-
 randombytes@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@@ -4016,7 +4006,7 @@ secp256k1@3.7.1:
     nan "^2.14.0"
     safe-buffer "^5.1.2"
 
-secp256k1@^3.6.2, secp256k1@^3.7.0:
+secp256k1@^3.6.2:
   version "3.8.0"
   resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-3.8.0.tgz#28f59f4b01dbee9575f56a47034b7d2e3b3b352d"
   integrity sha512-k5ke5avRZbtl9Tqx/SA7CbY3NF6Ro+Sj9cZxezFzuBlLDmyqPiL8hJJ+EmzD8Ig4LUDByHJ3/iPOVoRixs/hmw==
@@ -4653,14 +4643,6 @@ url-parse-lax@^3.0.0:
   dependencies:
     prepend-http "^2.0.0"
 
-url@^0.11.0:
-  version "0.11.0"
-  resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
-  integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=
-  dependencies:
-    punycode "1.3.2"
-    querystring "0.2.0"
-
 util-deprecate@^1.0.1, util-deprecate@~1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"