anchor.diff:  % 7. if at least one multicall succeeds, return updated tagdata, desturls
   
   -spec put_transaction(bool(), [binary()], #state{}) ->
  -    {'error', _} | {'ok', [node()], [binary()], binary()}.
  +    {'error', _} | {'ok', [node()], [binary()], tagcontent()}.
   put_transaction(false, _, _) -> {error, invalid_url_object};
   put_transaction(true, Urls, S) ->
       % XXX: compress data here!
       TagName = ddfs_util:pack_objname(S#state.tag, now()),
  -    TagData = make_tagdata(TagName, Urls, ddfs_util:format_timestamp()),
  -    put_distribute({TagName, TagData}).
  -
  --spec put_distribute({binary(), binary()}) ->
  -    {'error', _} | {'ok', [node()], [binary()], binary()}.
  -put_distribute(Msg) ->
  +    TagContent = #tagcontent{id = TagName,
  +                             urls = Urls,
  +                             last_modified = ddfs_util:format_timestamp()},
  +    TagData = make_tagdata(TagContent),
  +    put_distribute({TagName, TagData, TagContent}).
  +
  +-spec put_distribute({binary(), binary(), tagcontent()}) ->
  +    {'error', _} | {'ok', [node()], [binary()], tagcontent()}.
  +put_distribute({TagName, _, TagContent} = Msg) ->
       case put_distribute(Msg, get(tagk), [], []) of
  -        {ok, TagVol} -> put_commit(Msg, TagVol);
  +        {ok, TagVol} -> put_commit({TagName, TagContent}, TagVol);
           {error, _} = E -> E
       end.
   
  --spec put_distribute({binary(), binary()}, non_neg_integer(),
  +-spec put_distribute({binary(), binary(), tagcontent()}, non_neg_integer(),
       [{node(), binary()}], [node()]) ->
           {'error', _} | {'ok', [{node(), binary()}]}.
   put_distribute(_, K, OkNodes, _) when K == length(OkNodes) ->
       {ok, OkNodes};
   
  -put_distribute(Msg, K, OkNodes, Exclude) ->
  +put_distribute({TagName, TagData, _} = Msg, K, OkNodes, Exclude) ->
       TagMinK = get(min_tagk),
       K0 = K - length(OkNodes),
       {ok, Nodes} = gen_server:call(ddfs_master, {choose_nodes, K0, Exclude}),
  
positive.code: handle_cast({{update, Urls}, ReplyTo}, #state{data = deleted} = S) ->
      handle_cast({{put, urls, Urls}, ReplyTo}, S);
  
  handle_cast({{update, Urls}, ReplyTo}, #state{data = D} = S) ->
      % XXX: decompress data here!
      case parse_tagurls(D) of
          {error, _} = E ->
              send_replies(ReplyTo, E),
              {noreply, S, S#state.timeout};
          {ok, OldUrls} ->
              case validate_urls(Urls) of
                  true ->
                      handle_cast({{put, urls, Urls ++ OldUrls}, ReplyTo}, S);
                  false ->
                      send_replies(ReplyTo, {error, invalid_url_object}),
                      {noreply, S, S#state.timeout}
              end
      end;
  
  handle_cast({{put, Field, Value}, ReplyTo}, S) ->
  
negative.code:     handle_cast(M, S#state{url_cache = gb_sets:empty()});
  
  handle_cast(M, #state{url_cache = false, data = Data} = S) ->
      handle_cast(M, S#state{url_cache = gb_sets:from_list(Data#tagcontent.urls)});
  
  handle_cast({{insert_deleted, Url}, ReplyTo}, #state{url_cache = Deleted} = S) ->
      DeletedU = gb_sets:add(Url, Deleted),
  