15 releases (major breaking)

new 14.1.0 Oct 21, 2024
13.1.0 Jul 15, 2024
12.1.0 Jun 25, 2024
11.1.0 Jun 13, 2024
1.1.0 Oct 27, 2023

#1 in #encointer

Download history 313/week @ 2024-07-03 653/week @ 2024-07-10 436/week @ 2024-07-17 515/week @ 2024-07-24 496/week @ 2024-07-31 296/week @ 2024-08-07 298/week @ 2024-08-14 597/week @ 2024-08-21 272/week @ 2024-08-28 109/week @ 2024-09-04 123/week @ 2024-09-11 150/week @ 2024-09-18 225/week @ 2024-09-25 294/week @ 2024-10-02 326/week @ 2024-10-09 435/week @ 2024-10-16

1,301 downloads per month

GPL-3.0-or-later

210KB
4.5K SLoC

crates.io

pallet-encointer-bazaar

This crate is part of Encointer blockchain logic, built on substrate / polkadot-sdk

For high-level technical as well as use case documentation, please refer to our book

design

this is a design document WIP and does not reflect the current implementation at all times

class diagram

scope

The encointer bazaar shall

  • follow the GoodRelations vocabulary for e-commerce where possible
  • be compatible with schema.org json
    • LocalBusiness many shops may already have prepared this for google maps anyway
    • Product Any offered product or service. For example: a pair of shoes; a concert ticket; the rental of a car; a haircut; or an episode of a TV show streamed online.
    • Offer / Demand with a price and delivery method

custom rpc methods

For good API performance and dev experience, encointer nodes shall offer handy queries for bazaar. Usually, queries only concern one community

bazaar_getMyLocalBusinesses(who: AccountId)

returns: JsonArray<local_business: AccountId>

get all businesses controlled by who. Needs to iterate through all proxy accounts to see which ones delegate to who

bazaar_getLocalBusinesses(cid: CommunityIdentifier)

returns: JsonArray<(local_business: AccountId, json: url)>

get all business accounts for a specific community with their metadata url

bazaar_getOfferings(cid: CommunityIdentifier, local_business?: AccountId, since?: BlockNumber)

returns: JsonArray<(offering: url)>

optional:

  • local_business: only query offerings for that business
  • since: allows to filter by updated block height to query fresh entries only

Use Cases

  1. Business Management
    • create business
    • edit business
    • delete business
  2. Offering Management
    • create offering
    • edit offering
    • delete offering
  3. View (RPC)
    • businesses per community
    • offerings per community
    • offerings per businesses

Proposed Storage Model

BusinessRegistry: double_map(CommunityId, BusinessAccountId) -> (business_url, last_oid)
OfferingRegistry: double_map((CommunityId, BusinessAccountId), OfferingId) -> (offering_url)

BusinessAccountId is the public key of an anonymous proxy aka the business. The described tuples/triples are wrapped in value type objects.

Dispatchables Pseudo Code

createBusiness(CommunityId, business_url) {
  var BusinessAccountId = new AnonymousProxy(sender);
  BusinessRegistry.insert(CommunityId, 
                       BusinessAccountId, 
                       (business_url, 1, currrent_block_number));
}
  
updateBusiness(CommunityId, BusinessAccountId, new_business_url) {
  verify(BusinessAccountId, sender);
  BusinessRegistry.mutate(CommunityId, BusinessAccountId, (new_business_url, counter, current_block_number));
} 

deleteBusiness(CommunityId, BusinessAccountId) {
  verify(BusinessAccountId, sender);
  BusinessRegistry.remove(CommunityId, BusinessAccountId);
  OfferingRegistry.remove_prefix((CommunityId, BusinessId));
}

createOffering(CommunityId, BusinessAccountId, offering_url) {
  verify(BusinessAccountId, sender);
  var OfferingId = BusinessRegistry.get(CommunityId, BusinessAccountId).counter++
  OfferingRegistry.insert((CommunityId, BusinessAccountId), OfferingId, (offering_url, block_number));
  return OfferingId;
}
  
updateOffering(BusinessAccountId, OfferingId, new_offering_url) {
  verify(BusinessAccountId, sender);
  OfferingRegistry.mutate((CommunityId, BusinessAccountId), OfferingId, (BusinessAccountId, new_offering_url));
} 

deleteOffering(BusinessAccountId, OfferingId) {
  verify(BusinessAccountId, sender);
  OfferingRegistry.remove((CommunityId, BusinessAccountId), OfferingId);
}

RPCs

viewBusinesses(cid) {
  return BusinessRegistry.get(cid);
}
  
viewOfferings(cid) {
  return BusinessRegistry.get(cid).flatMap(bid -> OfferingRegistry.get(bid));
}

viewOfferings(bid) {
  return OfferingRegistry.get(bid);
}

Performance Considerations

The proposed implementation of viewOfferings for a certain business has O(n) complexity. It should only be evaluated through a off-chain rpc.

complexity Vec vs map for sets:

operation vec map
membership check DB Reads: O(1) Decoding: O(n) Search: O(log n) DB Reads: O(1)
update DB Writes: O(1) Encoding: O(n) DB Reads: O(1) Encoding: O(1) DB Writes: O(1)
iterate DB Reads: O(1) Decoding: O(n) Processing: O(n) DB Reads: O(n) Decoding: O(n) Processing: O(n)

iteration complexity (where vec would be better) is irrelevant because the bazaar pallet doesn't iterate

obviously, map scales better in terms of dispatchable weight for large n with no disadvantage for large number of calls with small n. Moreover, weight is constant, irrespective of n

the mobile app or web interface will usually fetch the entire set for a given cid, therefore Vec would be lighter to use. However, if we add a custom rpc api to fetch these sets, we can offer a convenient API while only putting a little more load on the API node

Dependencies

~22–36MB
~614K SLoC