Skip to content
This repository was archived by the owner on Apr 4, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# ref. https://goreleaser.com/customization/build/
builds:
- id: spaces-cli
main: ./cmd/spacescli
main: ./cmd/spaces-cli
binary: spaces-cli
flags:
- -v
Expand Down
44 changes: 37 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,23 @@ You could build...
### Wallet Support: `eth_typedSignedData`
TODO: Insert image of signing using MM

### Reserved Spaces
address space is reserved

### Content Addressing
SpacesVM verifies that values associated with keys that are length 66 (0x + hex-encoded keccak256 hash) are valid hashes.

### Fee Mechanisms
Claim Desirability + Decay Rate
FeeUnits vs Load Units vs Expiry Units (per action)
Expiry Rate vs Units

### Space Rewards
Lottery allocation X% of fee

### Genesis Allocation
One could easily modify this repository to instead send rewards to
beneficiaries chosen by whoever produces a block.

### Genesis Allocation -> public beta only
Airdrop `10,000 SPC` for anyone who has interacted with C-Chain more than
twice.

Expand All @@ -67,9 +75,14 @@ TODO: insert try spaces image + link
Hooked up to public beta

### spaces-cli
_To build the CLI, run `./scripts/build.sh`. It will be placed in `./build/spaces-cli` and
`$GOBIN/spaces-cli`._
#### Install
```bash
git clone https://github.com/ava-labs/spacesvm.git;
cd spacesvm;
go install -v ./cmd/spaces-cli;
```

#### Usage
```
SpacesVM CLI

Expand All @@ -89,6 +102,7 @@ Available Commands:
lifeline Extends the life of a given space
move Transfers a space to another address
network View information about this instance of the SpacesVM
owned Fetches all owned spaces for the address associated with the private key
resolve Reads a value at space/key
resolve-file Reads a file at space/key and saves it to disk
set Writes a key-value pair for the given space
Expand All @@ -104,7 +118,7 @@ Flags:
Use "spaces-cli [command] --help" for more information about a command.
```

#### Uploading Files
##### Uploading Files
```
spaces-cli set-file patrick ~/Downloads/computer.gif -> patrick/6fe5a52f52b34fb1e07ba90bad47811c645176d0d49ef0c7a7b4b22013f676c8
spaces-cli resolve-file patrick/6fe5a52f52b34fb1e07ba90bad47811c645176d0d49ef0c7a7b4b22013f676c8 computer_copy.gif
Expand Down Expand Up @@ -152,6 +166,8 @@ type Client interface {

// Recent actions on the network (sorted from recent to oldest)
RecentActivity() ([]*chain.Activity, error)
// All spaces owned by a given address
Owned(owner common.Address) ([]string, error)
}
```

Expand Down Expand Up @@ -400,6 +416,20 @@ transfer {timestamp,sender,txId,type,to,units}
reward {timestamp,txId,type,to,units}
```

#### spacesvm.owned
```
<<< POST
{
"jsonrpc": "2.0",
"method": "spacesvm.owned",
"params":{
"address":<hex encoded>
},
"id": 1
}
>>> {"spaces":[<string>]}
```

### Advanced Public Endpoints (`/public`)

#### spacesvm.suggestedRawFee
Expand Down Expand Up @@ -430,7 +460,7 @@ _Can use this to get the current fee rate._
```

## Running the VM
To build the VM, run `VM=true ./scripts/build.sh`.
To build the VM, run `./scripts/build.sh`.

### Joining the public beta
Put spacesvm binary in plugins dir
Expand All @@ -455,7 +485,7 @@ and creates a `spacesvm` genesis file. To build and run E2E tests, you need to s
See [`tests/e2e`](tests/e2e) and [`tests/runner`](tests/runner) to see how it's set up and how its client requests are made:

```bash
# to startup a cluster
# to startup a local cluster (good for development)
cd ${HOME}/go/src/github.com/ava-labs/spacesvm
./scripts/run.sh 1.7.4

Expand Down
28 changes: 28 additions & 0 deletions chain/claim_tx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,20 @@ func TestClaimTx(t *testing.T) {
}

// Cleanup DB after all txs submitted
senderSpaces, err := GetAllOwned(db, sender)
if err != nil {
t.Fatal(err)
}
if len(senderSpaces) != 0 {
t.Fatalf("sender owned spaces should = 0, found %d", len(senderSpaces))
}
sender2Spaces, err := GetAllOwned(db, sender2)
if err != nil {
t.Fatal(err)
}
if len(sender2Spaces) != 1 {
t.Fatalf("sender2 owned spaces should = 1, found %d", len(sender2Spaces))
}
if err := ExpireNext(db, 0, ClaimReward*10, true); err != nil {
t.Fatal(err)
}
Expand All @@ -135,4 +149,18 @@ func TestClaimTx(t *testing.T) {
if exists {
t.Fatal("space should not exist")
}
senderSpaces, err = GetAllOwned(db, sender)
if err != nil {
t.Fatal(err)
}
if len(senderSpaces) != 0 {
t.Fatal("owned spaces should be empty")
}
sender2Spaces, err = GetAllOwned(db, sender2)
if err != nil {
t.Fatal(err)
}
if len(sender2Spaces) != 0 {
t.Fatal("owned spaces should be empty")
}
}
2 changes: 1 addition & 1 deletion chain/move_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (m *MoveTx) Execute(c *TransactionContext) error {
i.Owner = m.To

// Update space
if err := MoveSpaceInfo(c.Database, []byte(m.Space), i); err != nil {
if err := MoveSpaceInfo(c.Database, c.Sender, []byte(m.Space), i); err != nil {
return err
}
return nil
Expand Down
91 changes: 81 additions & 10 deletions chain/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ import (
// -> [raw space]
// 0x6/ (space pruning queue)
// -> [raw space]
// 0x7/ (balance)
// -> [owner]=> balance
// 0x8/ (owned spaces)
// -> [owner]/[space]=> nil

const (
blockPrefix = 0x0
Expand All @@ -45,7 +49,8 @@ const (
keyPrefix = 0x4
expiryPrefix = 0x5
pruningPrefix = 0x6
balancePrefx = 0x7
balancePrefix = 0x7
ownedPrefix = 0x8

shortIDLen = 20

Expand Down Expand Up @@ -142,12 +147,23 @@ func PrefixPruningKey(expired uint64, rspace ids.ShortID) (k []byte) {
// [balancePrefix] + [delimiter] + [address]
func PrefixBalanceKey(address common.Address) (k []byte) {
k = make([]byte, 2+common.AddressLength)
k[0] = balancePrefx
k[0] = balancePrefix
k[1] = parser.ByteDelimiter
copy(k[2:], address[:])
return
}

// [ownedPrefix] + [delimiter] + [address] + [delimiter] + [space]
func PrefixOwnedKey(address common.Address, space []byte) (k []byte) {
k = make([]byte, 2+common.AddressLength+1+len(space))
k[0] = ownedPrefix
k[1] = parser.ByteDelimiter
copy(k[2:], address[:])
k[2+common.AddressLength] = parser.ByteDelimiter
copy(k[2+common.AddressLength+1:], space)
return
}

const specificTimeKeyLen = 2 + 8 + 1 + shortIDLen

// [expiry/pruningPrefix] + [delimiter] + [timestamp] + [delimiter] + [rawSpace]
Expand Down Expand Up @@ -402,19 +418,26 @@ func ExpireNext(db database.Database, rparent int64, rcurrent int64, bootstrappe
return err
}

// [space]
spc := cursor.Value()
// [owner] + [space]
expiryValue := cursor.Value()
owner := common.BytesToAddress(expiryValue[:common.AddressLength])
space := expiryValue[common.AddressLength:]

// Update owned prefix
if err := db.Delete(PrefixOwnedKey(owner, space)); err != nil {
return err
}

// [infoPrefix] + [delimiter] + [space]
k := SpaceInfoKey(spc)
k := SpaceInfoKey(space)
if err := db.Delete(k); err != nil {
return err
}

expired, rspc, err := extractSpecificTimeKey(curKey)
if err != nil {
return err
}

if bootstrapped {
// [pruningPrefix] + [delimiter] + [timestamp] + [delimiter] + [rawSpace]
k = PrefixPruningKey(expired, rspc)
Expand All @@ -428,7 +451,7 @@ func ExpireNext(db database.Database, rparent int64, rcurrent int64, bootstrappe
return err
}
}
log.Debug("space expired", "space", string(spc))
log.Debug("space expired", "space", string(space))
}
return nil
}
Expand Down Expand Up @@ -486,13 +509,26 @@ func HasSpaceKey(db database.KeyValueReader, space []byte, key []byte) (bool, er
return db.Has(k)
}

func ExpiryDataValue(address common.Address, space []byte) (v []byte) {
v = make([]byte, common.AddressLength+len(space))
copy(v, address[:])
copy(v[common.AddressLength:], space)
return v
}

func PutSpaceInfo(db database.KeyValueWriter, space []byte, i *SpaceInfo, lastExpiry uint64) error {
// If [RawSpace] is empty, this is a new space.
if i.RawSpace == ids.ShortEmpty {
rspace, err := RawSpace(space, i.Created)
if err != nil {
return err
}
i.RawSpace = rspace

// Only store the owner on creation
if err := db.Put(PrefixOwnedKey(i.Owner, space), nil); err != nil {
return err
}
}
if lastExpiry > 0 {
// [expiryPrefix] + [delimiter] + [timestamp] + [delimiter] + [rawSpace]
Expand All @@ -503,7 +539,7 @@ func PutSpaceInfo(db database.KeyValueWriter, space []byte, i *SpaceInfo, lastEx
}
// [expiryPrefix] + [delimiter] + [timestamp] + [delimiter] + [rawSpace]
k := PrefixExpiryKey(i.Expiry, i.RawSpace)
if err := db.Put(k, space); err != nil {
if err := db.Put(k, ExpiryDataValue(i.Owner, space)); err != nil {
return err
}
// [infoPrefix] + [delimiter] + [space]
Expand All @@ -517,14 +553,28 @@ func PutSpaceInfo(db database.KeyValueWriter, space []byte, i *SpaceInfo, lastEx

// MoveSpaceInfo should only be used if the expiry isn't changing and
// [SpaceInfo] is already in the database.
func MoveSpaceInfo(db database.KeyValueReaderWriter, space []byte, i *SpaceInfo) error {
func MoveSpaceInfo(
db database.KeyValueWriter, oldOwner common.Address,
space []byte, i *SpaceInfo,
) error {
// [infoPrefix] + [delimiter] + [space]
k := SpaceInfoKey(space)
b, err := Marshal(i)
if err != nil {
return err
}
return db.Put(k, b)
if err := db.Put(k, b); err != nil {
return err
}
// Updated owned prefix
if err := db.Delete(PrefixOwnedKey(oldOwner, space)); err != nil {
return err
}
if err := db.Put(PrefixOwnedKey(i.Owner, space), nil); err != nil {
return err
}
k = PrefixExpiryKey(i.Expiry, i.RawSpace)
return db.Put(k, ExpiryDataValue(i.Owner, space))
}

type ValueMeta struct {
Expand Down Expand Up @@ -676,3 +726,24 @@ func ApplyReward(
log.Debug("skipping reward: no valid space")
return common.Address{}, false, nil
}

func GetAllOwned(db database.Database, owner common.Address) (spaces []string, err error) {
baseKey := PrefixOwnedKey(owner, nil)
cursor := db.NewIteratorWithStart(baseKey)
spaces = []string{}
for cursor.Next() {
curKey := cursor.Key()
if bytes.Compare(baseKey, curKey) < -1 { // startKey < curKey; continue search
continue
}
if !bytes.Contains(curKey, baseKey) { // curKey does not contain base key; end search
break
}

spaces = append(spaces,
// [ownedPrefix] + [delimiter] + [address] + [delimiter] + [space]
string(curKey[2+common.AddressLength+1:]),
)
}
return spaces, nil
}
16 changes: 16 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ type Client interface {

// Recent actions on the network (sorted from recent to oldest)
RecentActivity() ([]*chain.Activity, error)
// All spaces owned by a given address
Owned(owner common.Address) ([]string, error)
}

// New creates a new client object.
Expand Down Expand Up @@ -290,3 +292,17 @@ func (cli *client) RecentActivity() (activity []*chain.Activity, err error) {
}
return resp.Activity, nil
}

func (cli *client) Owned(addr common.Address) (spaces []string, err error) {
resp := new(vm.OwnedReply)
if err = cli.req.SendRequest(
"owned",
&vm.OwnedArgs{
Address: addr,
},
resp,
); err != nil {
return nil, err
}
return resp.Spaces, nil
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading