Skip to content

Commit 70d231b

Browse files
authored
Merge pull request ethereum#194 from ethersphere/swarm-mutableresource-index
swarm/storage: Improved versioning and reverse lookups for mutable resources
2 parents ddfc0a2 + 54da86d commit 70d231b

File tree

2 files changed

+173
-70
lines changed

2 files changed

+173
-70
lines changed

swarm/storage/resource.go

Lines changed: 118 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ type resource struct {
2929
name string
3030
ensName common.Hash
3131
startBlock uint64
32-
lastBlock uint64
32+
lastPeriod uint32
3333
frequency uint64
34-
version uint64
34+
version uint32
3535
data []byte
3636
updated time.Time
3737
}
@@ -102,7 +102,7 @@ type ResourceHandler struct {
102102
ethapi *rpc.Client
103103
resources map[string]*resource
104104
hashLock sync.Mutex
105-
resourceLock sync.Mutex
105+
resourceLock sync.RWMutex
106106
hasher SwarmHash
107107
privKey *ecdsa.PrivateKey
108108
maxChunkData int64
@@ -168,7 +168,7 @@ func NewResource(name string, startBlock uint64, frequency uint64) (*resource, e
168168
}, nil
169169
}
170170

171-
// Creates a new root entry for a resource update identified by `name` with the specified `frequency`.
171+
// Creates a new root entry for a mutable resource identified by `name` with the specified `frequency`.
172172
//
173173
// The start block of the resource update will be the actual current block height of the connected network.
174174
func (self *ResourceHandler) NewResource(name string, frequency uint64) (*resource, error) {
@@ -202,15 +202,15 @@ func (self *ResourceHandler) NewResource(name string, frequency uint64) (*resour
202202
self.Put(chunk)
203203
log.Debug("new resource", "name", validname, "key", ensName, "startBlock", currentblock, "frequency", frequency)
204204

205-
self.resourceLock.Lock()
206-
defer self.resourceLock.Unlock()
207-
self.resources[name] = &resource{
205+
rsrc := &resource{
208206
name: validname,
209207
ensName: ensName,
210208
startBlock: currentblock,
211209
frequency: frequency,
212210
updated: time.Now(),
213211
}
212+
self.setResource(name, rsrc)
213+
214214
return self.resources[name], nil
215215
}
216216

@@ -258,12 +258,12 @@ func (self *ResourceHandler) SetResource(rsrc *resource, allowOverwrite bool) er
258258
// root chunk.
259259
// It is the callers responsibility to make sure that this chunk exists (if the resource
260260
// update root data was retrieved externally, it typically doesn't)
261-
func (self *ResourceHandler) LookupVersion(name string, nextblock uint64, version uint64, refresh bool) (*resource, error) {
261+
func (self *ResourceHandler) LookupVersion(name string, period uint32, version uint32, refresh bool) (*resource, error) {
262262
rsrc, err := self.loadResource(name, refresh)
263263
if err != nil {
264264
return nil, err
265265
}
266-
return self.lookup(rsrc, name, nextblock, version, refresh)
266+
return self.lookup(rsrc, name, period, version, refresh)
267267
}
268268

269269
// Retrieves the latest version of the resource update identified by `name`
@@ -274,12 +274,12 @@ func (self *ResourceHandler) LookupVersion(name string, nextblock uint64, versio
274274
// and returned.
275275
//
276276
// See also (*ResourceHandler).LookupVersion
277-
func (self *ResourceHandler) LookupHistorical(name string, nextblock uint64, refresh bool) (*resource, error) {
277+
func (self *ResourceHandler) LookupHistorical(name string, period uint32, refresh bool) (*resource, error) {
278278
rsrc, err := self.loadResource(name, refresh)
279279
if err != nil {
280280
return nil, err
281281
}
282-
return self.lookup(rsrc, name, nextblock, 0, refresh)
282+
return self.lookup(rsrc, name, period, 0, refresh)
283283
}
284284

285285
// Retrieves the latest version of the resource update identified by `name`
@@ -303,15 +303,15 @@ func (self *ResourceHandler) LookupLatest(name string, refresh bool) (*resource,
303303
if err != nil {
304304
return nil, err
305305
}
306-
nextblock := getNextBlock(rsrc.startBlock, currentblock, rsrc.frequency)
307-
return self.lookup(rsrc, name, nextblock, 0, refresh)
306+
nextperiod := getNextPeriod(rsrc.startBlock, currentblock, rsrc.frequency)
307+
return self.lookup(rsrc, name, nextperiod, 0, refresh)
308308
}
309309

310310
// base code for public lookup methods
311-
func (self *ResourceHandler) lookup(rsrc *resource, name string, nextblock uint64, version uint64, refresh bool) (*resource, error) {
311+
func (self *ResourceHandler) lookup(rsrc *resource, name string, period uint32, version uint32, refresh bool) (*resource, error) {
312312

313-
if nextblock == 0 {
314-
return nil, fmt.Errorf("blocknumber must be >0")
313+
if period == 0 {
314+
return nil, fmt.Errorf("period must be >0")
315315
}
316316

317317
// start from the last possible block period, and iterate previous ones until we find a match
@@ -323,29 +323,29 @@ func (self *ResourceHandler) lookup(rsrc *resource, name string, nextblock uint6
323323
version = 1
324324
}
325325

326-
for nextblock > rsrc.startBlock {
327-
key := self.resourceHash(rsrc.ensName, nextblock, version)
326+
for period > 0 {
327+
key := self.resourceHash(rsrc.ensName, period, version)
328328
chunk, err := self.Get(key)
329329
if err == nil {
330330
if specificversion {
331-
return self.updateResourceIndex(rsrc, chunk, nextblock, version, &name)
331+
return self.updateResourceIndex(rsrc, chunk, &name)
332332
}
333333
// check if we have versions > 1. If a version fails, the previous version is used and returned.
334-
log.Trace("rsrc update version 1 found, checking for version updates", "nextblock", nextblock, "key", key)
334+
log.Trace("rsrc update version 1 found, checking for version updates", "period", period, "key", key)
335335
for {
336336
newversion := version + 1
337-
key := self.resourceHash(rsrc.ensName, nextblock, newversion)
337+
key := self.resourceHash(rsrc.ensName, period, newversion)
338338
newchunk, err := self.Get(key)
339339
if err != nil {
340-
return self.updateResourceIndex(rsrc, chunk, nextblock, version, &name)
340+
return self.updateResourceIndex(rsrc, chunk, &name)
341341
}
342-
log.Trace("version update found, checking next", "version", version, "block", nextblock, "key", key)
342+
log.Trace("version update found, checking next", "version", version, "period", period, "key", key)
343343
chunk = newchunk
344344
version = newversion
345345
}
346346
}
347-
log.Trace("rsrc update not found, checking previous period", "block", nextblock, "key", key)
348-
nextblock -= rsrc.frequency
347+
log.Trace("rsrc update not found, checking previous period", "period", period, "key", key)
348+
period--
349349
}
350350
return nil, fmt.Errorf("no updates found")
351351
}
@@ -355,12 +355,9 @@ func (self *ResourceHandler) loadResource(name string, refresh bool) (*resource,
355355
// if the resource is not known to this session we must load it
356356
// if refresh is set, we force load
357357

358-
rsrc := &resource{}
359-
360-
self.resourceLock.Lock()
361-
_, ok := self.resources[name]
362-
self.resourceLock.Unlock()
363-
if !ok || refresh {
358+
rsrc := self.getResource(name)
359+
if rsrc == nil || refresh {
360+
rsrc = &resource{}
364361
// make sure our ens identifier is idna safe
365362
validname, err := idna.ToASCII(name)
366363
if err != nil {
@@ -397,7 +394,7 @@ func (self *ResourceHandler) loadResource(name string, refresh bool) (*resource,
397394
}
398395

399396
// update mutable resource index map with specified content
400-
func (self *ResourceHandler) updateResourceIndex(rsrc *resource, chunk *Chunk, nextblock uint64, version uint64, indexname *string) (*resource, error) {
397+
func (self *ResourceHandler) updateResourceIndex(rsrc *resource, chunk *Chunk, indexname *string) (*resource, error) {
401398

402399
// rsrc update data chunks are total hacks
403400
// and have no size prefix :D
@@ -407,18 +404,36 @@ func (self *ResourceHandler) updateResourceIndex(rsrc *resource, chunk *Chunk, n
407404
}
408405

409406
// update our rsrcs entry map
410-
rsrc.lastBlock = nextblock
407+
period, version, _, data, err := parseUpdate(chunk.SData[signatureLength:])
408+
rsrc.lastPeriod = period
411409
rsrc.version = version
412-
rsrc.data = make([]byte, len(chunk.SData)-signatureLength)
413410
rsrc.updated = time.Now()
414-
copy(rsrc.data, chunk.SData[signatureLength:])
415-
log.Debug("Resource synced", "name", rsrc.name, "key", chunk.Key, "block", nextblock, "version", version)
416-
self.resourceLock.Lock()
417-
self.resources[*indexname] = rsrc
418-
self.resourceLock.Unlock()
411+
rsrc.data = make([]byte, len(data))
412+
copy(rsrc.data, data)
413+
log.Debug("Resource synced", "name", rsrc.name, "key", chunk.Key, "period", rsrc.lastPeriod, "version", rsrc.version)
414+
self.setResource(*indexname, rsrc)
419415
return rsrc, nil
420416
}
421417

418+
func parseUpdate(blob []byte) (period uint32, version uint32, ensname []byte, data []byte, err error) {
419+
headerlength := binary.LittleEndian.Uint16(blob[:2])
420+
if int(headerlength+2) > len(blob) {
421+
return 0, 0, nil, nil, fmt.Errorf("Reported header length %d longer than actual data length %d", headerlength, len(blob))
422+
}
423+
cursor := 2
424+
period = binary.LittleEndian.Uint32(blob[cursor : cursor+4])
425+
cursor += 4
426+
version = binary.LittleEndian.Uint32(blob[cursor : cursor+4])
427+
cursor += 4
428+
namelength := int(headerlength) - cursor + 2
429+
ensname = make([]byte, namelength)
430+
copy(ensname, blob[cursor:])
431+
cursor += namelength
432+
data = make([]byte, len(blob)-cursor)
433+
copy(data, blob[cursor:])
434+
return
435+
}
436+
422437
// Adds an actual data update
423438
//
424439
// Uses the data currently loaded in the resources map entry.
@@ -447,28 +462,48 @@ func (self *ResourceHandler) Update(name string, data []byte) (Key, error) {
447462
if err != nil {
448463
return nil, err
449464
}
450-
nextblock := getNextBlock(resource.startBlock, currentblock, resource.frequency)
465+
nextperiod := getNextPeriod(resource.startBlock, currentblock, resource.frequency)
451466

452467
// if we already have an update for this block then increment version
453-
var version uint64
454-
if nextblock == resource.lastBlock {
468+
var version uint32
469+
if self.hasUpdate(name, nextperiod) {
455470
version = resource.version
456471
}
457472
version++
458473

474+
// prepend version and period to allow reverse lookups
475+
// data header length does NOT include the header length prefix bytes themselves
476+
headerlength := uint16(len(resource.ensName) + 4 + 4)
477+
fulldata := make([]byte, int(headerlength)+2+len(data))
478+
479+
cursor := 0
480+
binary.LittleEndian.PutUint16(fulldata, headerlength)
481+
cursor += 2
482+
483+
binary.LittleEndian.PutUint32(fulldata[cursor:], nextperiod)
484+
cursor += 4
485+
486+
binary.LittleEndian.PutUint32(fulldata[cursor:], version)
487+
cursor += 4
488+
489+
copy(fulldata[cursor:], resource.ensName[:])
490+
cursor += len(resource.ensName)
491+
492+
copy(fulldata[cursor:], data)
493+
459494
// create the update chunk and send it
460-
key := self.resourceHash(resource.ensName, nextblock, version)
495+
key := self.resourceHash(resource.ensName, nextperiod, version)
461496
chunk := NewChunk(key, nil)
462-
chunk.SData, err = self.signContent(data)
497+
chunk.SData, err = self.signContent(fulldata)
463498
if err != nil {
464499
return nil, err
465500
}
466-
chunk.Size = int64(len(data))
501+
chunk.Size = int64(len(fulldata))
467502
self.Put(chunk)
468-
log.Trace("resource update", "name", resource.name, "key", key, "currentblock", currentblock, "lastBlock", nextblock, "version", version)
503+
log.Trace("resource update", "name", resource.name, "key", key, "currentblock", currentblock, "lastperiod", nextperiod, "version", version, "data", chunk.SData)
469504

470505
// update our resources map entry and return the new key
471-
resource.lastBlock = nextblock
506+
resource.lastPeriod = nextperiod
472507
resource.version = version
473508
resource.data = make([]byte, len(data))
474509
copy(resource.data, data)
@@ -494,20 +529,37 @@ func (self *ResourceHandler) getBlock() (uint64, error) {
494529
return strconv.ParseUint(currentblock, 10, 64)
495530
}
496531

497-
func (self *ResourceHandler) resourceHash(namehash common.Hash, blockheight uint64, version uint64) Key {
498-
// format is: hash(namehash|blockheight|version)
532+
func (self *ResourceHandler) BlockToPeriod(name string, blocknumber uint64) uint32 {
533+
return getNextPeriod(self.resources[name].startBlock, blocknumber, self.resources[name].frequency)
534+
}
535+
536+
func (self *ResourceHandler) PeriodToBlock(name string, period uint32) uint64 {
537+
return self.resources[name].startBlock + (uint64(period) * self.resources[name].frequency)
538+
}
539+
540+
func (self *ResourceHandler) getResource(name string) *resource {
541+
self.resourceLock.RLock()
542+
defer self.resourceLock.RUnlock()
543+
rsrc := self.resources[name]
544+
return rsrc
545+
}
546+
547+
func (self *ResourceHandler) setResource(name string, rsrc *resource) {
548+
self.resourceLock.Lock()
549+
defer self.resourceLock.Unlock()
550+
self.resources[name] = rsrc
551+
}
552+
553+
func (self *ResourceHandler) resourceHash(namehash common.Hash, period uint32, version uint32) Key {
554+
// format is: hash(namehash|period|version)
499555
self.hashLock.Lock()
500556
defer self.hashLock.Unlock()
501557
self.hasher.Reset()
502558
self.hasher.Write(namehash[:])
503-
b := make([]byte, 8)
504-
c := binary.PutUvarint(b, blockheight)
559+
b := make([]byte, 4)
560+
binary.LittleEndian.PutUint32(b, period)
505561
self.hasher.Write(b)
506-
// PutUvarint only overwrites first c bytes
507-
for i := 0; i < c; i++ {
508-
b[i] = 0
509-
}
510-
c = binary.PutUvarint(b, version)
562+
binary.LittleEndian.PutUint32(b, version)
511563
self.hasher.Write(b)
512564
return self.hasher.Sum(nil)
513565
}
@@ -554,6 +606,13 @@ func (self *ResourceHandler) verifyContent(chunkdata []byte) error {
554606
return nil
555607
}
556608

609+
func (self *ResourceHandler) hasUpdate(name string, period uint32) bool {
610+
if self.resources[name].lastPeriod == period {
611+
return true
612+
}
613+
return false
614+
}
615+
557616
type resourceChunkStore struct {
558617
localStore ChunkStore
559618
netStore ChunkStore
@@ -596,8 +655,8 @@ func (r *resourceChunkStore) Close() {
596655
r.localStore.Close()
597656
}
598657

599-
func getNextBlock(start uint64, current uint64, frequency uint64) uint64 {
658+
func getNextPeriod(start uint64, current uint64, frequency uint64) uint32 {
600659
blockdiff := current - start
601-
periods := (blockdiff / frequency) + 1
602-
return start + (frequency * periods)
660+
period := blockdiff / frequency
661+
return uint32(period + 1)
603662
}

0 commit comments

Comments
 (0)