I am running a multi_index in my contract and want to include an abi action that will clear it. Is there a clever way to delete all records? At the moment I am using something like this:

//@abi action        
void droptable(){
    myIndex example(_self, _self); // code, scope
    for(auto itr = example.begin(); itr != example.end();) {
        itr = example.erase(itr);

What I fear is, that if the index is too large, the valid processing time will overrun and the action will get cancelled. Is there an API call or something I am missing?

  • 1
    I can tell you from experience that erasing by iterating will overrun the processing time for large row counts/row sizes. Jun 28 '18 at 17:16
  • what't the max process time a transaction can use?@JohnHaager
    – Jimmy Guo
    Jun 29 '18 at 13:06
  • 2
    Transactions have to complete within 150ms. Otherwise they are terminated and fail. Jun 29 '18 at 15:45

After I didn't find any other solution, I ended up with a sequential workaround which will delete all records by time.

Disclaimer: Be sure to block all other actions that would rely on the index, because data will invalidate during deletion and new data maybe deleted as well. And you may add some authorization because not everybody should be able to delete all records.

I am using two indices from which I want to delete all records. The user has the possibility to define how much elements shall be deleted per index per transaction. I did not want to hardcode the limit. This gives the possibillity to heuristically find the maximum elements that can be deleted before a transaction time limit kicks in.

// in your .hpp
//@abi table testindex1 i64
struct teststruct {
  uint64_t     id;
  account_name owner;

  auto primary_key() const { return id; }

  EOSLIB_SERIALIZE( limit_order, ( id )( owner ) )

typedef multi_index<N(testindex1), teststruct> testIndex1;

// same for testIndex2

//In your .cpp 

// pLimit is the (max) number of elements
// which shall be deleted in each index
// before the next transaction is created

void MyContract::dtseq(uint64_t pLimit, uint128_t pLastId){
    // block or it will run forever
    if(pLimit > 0){
        testIndex1 test1(_self, _self); // code, scope
        uint64_t count = 0;

        // iterate over first index 
        for(auto itr = test1.begin(); itr != test1.end() && count!=pLimit;) {
            // delete element and update iterator reference
            itr = test1.erase(itr);

        // iterate over second index 
        testIndex2 test2(_self, _self); // code, scope
        count = 0;
        for(auto itr = test2.begin(); itr != test2.end() && count!=pLimit;) {
            // delete element and update iterator reference
            itr = test2.erase(itr);

        // are elements left in one of the indices?
        if(test1.begin() != test1.end() || test2.begin() != test2.end()){
                // build new transactions which will call the same function
                uint64_t time = current_time();
                checksum256 calc_hash; // fc::sha256
                uint128_t toHash, id2;

                toHash = ((uint128_t(_self) << 64) | uint128_t(time));
                // If not the first call use the pLastId to generate unique 
                if(pLastId != 0){
                    toHash = toHash | pLastId;

                // Build a Hash so the transaction ids get distinct
                sha256(reinterpret_cast<char *>(&toHash), 128, &calc_hash); 
                id2 = *reinterpret_cast<uint128_t *>(&calc_hash);

                // First Transaction with unhashed id and passed id as input
                transaction out;
                out.actions.emplace_back(permission_level{_self, N(active)}, _self, N(dtseq), std::make_tuple(pLimit, toHash));
                out.send(toHash, _self);

                // Second Transction with hashed id and passed id as input
                transaction out2;

                out2.actions.emplace_back(permission_level{_self, N(active)}, _self, N(dtseq), std::make_tuple(pLimit, id2));
                out2.send(id2, _self);

You can surely optimize by optimizing the loop logic or profit from parallel execution (when it is released). Also error messages and asserts should be included. Consider this as a demo and use it like you want to.

EDIT - Flooding/Recursive Improvement - High Speed Up and much more reliable

I discovered that sometimes the deferred transactions get cancelled and it takes much time to delete all records. Using this flooding approach by creating always 2 times more the tables will clear in very short time.

Call it with something like this. It will delete 7 entries in both indices in each call and create 14 deletions more in the next step. Then 28 and so on.

 cleos push action mycontract dtseq '['7', '0']' -p mycontract@active 
  • I am getting errors for test-indexes, as unknown types. There isn't anything similar in eos-master. Could you please provide more references to a code ? or was it just for an example ? Jul 15 '18 at 13:14
  • I guess you need to use typedef in your hpp file to define testIndex1/2. Also keep in mind that this solution is not guaranteed to run through all entries because it uses deferred transactions.
    – TeeAttack42
    Jul 15 '18 at 13:20
  • OK, so what does typedef define? Any code example ? Without proper definition of variables I cannot use the code. Jul 15 '18 at 13:24
  • OK I got what you mean... therefore the code is to be used as an example only and not as an actual solution. Jul 15 '18 at 13:36
  • 2
    Like I have written: consider this as a demo Because I don't know what kind of index you are using. This is something that depends on your contract. But you got a point there I will add an example typedef in the anwser.
    – TeeAttack42
    Jul 15 '18 at 13:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.