关于NEAR collections 的思考笔记

链上状态存储

链上状态均以 key - value 键值对形式存储,通常包括「合约状态」和「容器状态」,调用合约方法时会反序列化「合约状态」到内存,但不会反序列化「容器状态」,合约方法调用完毕后会序列化「合约状态」回链上

  • 合约状态:指 near_bindgen 修饰的 Contract 对象存储的链上状态,也是 env::state_read 函数返回的状态,以一对 key - value 键值对形式存储 Contract 对象内的数据,并且 key 的值为 'STATE',对应的 base64 值为 'U1RBVEU='

    // alice
    #[near_bindgen]
    #[derive(BorshDeserialize,BorshSerialize)]
    struct Contract {}
    
    impl Default for Contract {
       fn default() -> Self {
          Self {}
       }
    }
    near view-state alice --finality final
    
    [ 
        { key: 'U1RBVEU='', value: '' } // 「合约状态」为空,因为 Contract 对象内没有属性
    ]
  • 容器状态:指使用 near_sdk::collections 存储的链上状态,以多对 key - value 键值对形式存储容器内数据,除了合约状态以外剩下的通常都是容器状态

    // bob
    #[near_bindgen]
    #[derive(BorshDeserialize,BorshSerialize,PanicOnDefault)]
    struct Contract {
        vec: Vector
    }
    
    #[near_bindgen]
    impl Contract {
        #[init]
        #[private]
        pub fn init() -> Self {
            Self {
                vec: Vector::new(b'v')
            }
        }
      
        pub fn add(&mut self, value: String) {
            self.vec.push(&value);
        }
    }
    near call bob init --account-id bob
    near call bob add '{"value":"hello"}' --account-id bob
    near call bob add '{"value":"world"}' --account-id bob
    near view-state bob --finality final
    
    [
      { key: 'U1RBVEU=', value: 'AgAAAAAAAAABAAAAdg==' }, // 「合约状态」不为空,包括了 Vector,不包括 Vector 内的数据
      { key: 'dgAAAAAAAAAA', value: 'BQAAAGhlbGxv' },     // 「容器状态」,Vector 中存储的 "hello"
      { key: 'dgEAAAAAAAAA', value: 'BQAAAHdvcmxk' }      // 「容器状态」,Vector 中存储的 "world"
    ]

    collections

    基础 collections

    • LookupMap

      * 无法迭代
      * 大量数据且无需迭代的情况下使用
      
      每存一份数据:
      KEY, VALUE
      在链上对应存储:
      { key: key_prefix + KEY, value: VALUE }
    • LookupSet

      * 无法迭代
      * 大量数据且无需迭代的情况下使用
      
      每存一份数据:
      VALUE
      在链上对应存储:
      { key: key_prefix + VALUE, value: VALUE }
    • Vector

      * 可以迭代
      * 无法排序
      * 大量数据且无需排序的情况下使用
      
      每存一份数据:
      VALUE
      在链上对应存储:
      { key: key_prefix + VALUE_INDEX, value: VALUE }
    • LazyOption

      * 不经常从链上反序列化的情况下使用
      * 所有 collections 本质上都是 LazyOption
      
      保存的数据:
      VALUE
      在链上对应存储:
      { key: key_prefix, value: VALUE }

    派生 collections

    • UnorderedSet

      * 可以迭代
      * 大量数据且需要迭代的情况下使用
      
      每存一份数据:
      VALUE
      在链上对应存储:
      { key: key_prefix + 'i' + VALUE, value: VALUE_INDEX }    // LookupMap for VALUE_INDEX
      { key: key_prefix + 'e' + VALUE_INDEX, value: VALUE }    // Vector for VALUE
      
      UnorderedSet 基于一个 Vector 实现,用于存储 VALUE,其中 VALUE 在 Vector 中的索引值 VALUE_INDEX 也以 key - value 键值对形式直接存储在链上(相当于一个 LookupMap)。 UnoederedSet 的存储占用为 LookupSet 的两倍
    • UnorderedMap

      * 可以迭代
      * 大量数据且需要迭代的情况下使用
      
      每存一份数据:
      KEY, VALUE
      在链上对应存储:
      { key: key_prefix + 'i' + KEY, value: KEY_INDEX }    // LookupMap for KEY_INDEX
      { key: key_prefix + 'k' + KEY_INDEX, value: KEY }    // Vector for KEY
      { key: key_prefix + 'v' + KEY_INDEX, value: VALUE }  // Vector for VALUE
      
      UnorderedMap 基于两个 Vector 实现,分别用于存储 KEY 和 VALUE,其中 KEY 在 Vector 中的索引值 KEY_INDEX 也以 key - value 键值对形式直接存储在链上(相当于一个 LookupMap)。 UnoederedMap 的存储占用为 LookupMap 的三倍
    • TreeMap

      * 可以迭代
      * 大量数据且需要迭代的情况下使用
      
      每存一份数据:
      KEY, VALUE
      在链上对应存储:
      { key: key_prefix + 'v' + KEY, value: VALUE }               // LookupMap for VALUE
      { key: key_prefix + 'n' + NODE_INDEX, value: NODE }    // Vector for NODE
      
      TreeMap 基于一个 LookupMap 和一个 Vector 实现,LookupMap 用于直接存储 KEY 和 VALUE,Vector 用于存储一棵 AVL 树,树的节点保存 KEY。TreeMap 插入删除效率不如 UnorderedMap 但存储占用比 UnorderedMap 更小

    collections 注意事项

    • collections 中所有 get 方法都是直接从链上反序列化数据到内存,因此返回的是

      Option

      而不是

      Option<&T>

      修改完的数据需要重新 insert 或 replace 回 collections

    • 当 collection 嵌套 collection,如:

      #[near_bindgen]
      #[derive(BorshDeserialize,BorshSerialize)]
      struct Contract {
          data: LookupMap>
      }
      1. 拿到 Vector 中的数据修改完后需要 replace Vector 中原来的数据,但 Vector 不需要 insert 回 LookupMap
      2. 向 Vector push 或 remove 数据后,需要将 Vector insert 回 LookupMap,因为 Vector 的长度发生了改变,Vector 的属性通过 LookupMap 存储在链上的一对 key - value 中,需要修改这对 key - value,否则会出现状态不一致。建议无论是修改数据还是新增、删除数据,都将 Vector insert 回 LookupMap,以避免出错

你可能感兴趣的:(near)