mirror of https://github.com/poanetwork/gecko.git
init repo
This commit is contained in:
commit
b9e34e3b15
|
@ -0,0 +1,14 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -ev
|
||||||
|
|
||||||
|
bash <(curl -s https://codecov.io/bash)
|
||||||
|
|
||||||
|
docker tag $DOCKERHUB_REPO:$COMMIT $DOCKERHUB_REPO:travis-$TRAVIS_BUILD_NUMBER
|
||||||
|
|
||||||
|
if [ "${TRAVIS_EVENT_TYPE}" == "push" ] && [ "${TRAVIS_BRANCH}" == "platform" ]; then
|
||||||
|
docker tag $DOCKERHUB_REPO:$COMMIT $DOCKERHUB_REPO:$TRAVIS_BRANCH
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$DOCKER_PASSWORD" | docker login --username "$DOCKER_USERNAME" --password-stdin
|
||||||
|
docker push $DOCKERHUB_REPO
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -ev
|
||||||
|
|
||||||
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
|
||||||
|
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get -y -o Dpkg::Options::="--force-confnew" install docker-ce
|
||||||
|
# hack to address problem with using DOCKER_BUILDKIT=1, inspired by:
|
||||||
|
# * https://github.com/rootless-containers/usernetes/blob/master/.travis.yml
|
||||||
|
#
|
||||||
|
# links discussing the issue:
|
||||||
|
# * https://github.com/moby/buildkit/issues/606#issuecomment-453959632
|
||||||
|
# * https://travis-ci.community/t/docker-builds-are-broken-if-buildkit-is-used-docker-buildkit-1/2994
|
||||||
|
# * https://github.com/moby/moby/issues/39120
|
||||||
|
sudo docker --version
|
||||||
|
sudo cat /etc/docker/daemon.json
|
||||||
|
sudo rm -f /etc/docker/daemon.json
|
||||||
|
sudo systemctl restart docker
|
|
@ -0,0 +1,18 @@
|
||||||
|
codecov:
|
||||||
|
branch: platform
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
range: 60..100
|
||||||
|
round: down
|
||||||
|
precision: 5
|
||||||
|
status:
|
||||||
|
project:
|
||||||
|
default:
|
||||||
|
threshold: 5%
|
||||||
|
patch:
|
||||||
|
default:
|
||||||
|
threshold: 50%
|
||||||
|
|
||||||
|
comment:
|
||||||
|
layout: "header, diff, changes, sunburst, uncovered"
|
||||||
|
behavior: default
|
|
@ -0,0 +1,26 @@
|
||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior.
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Operating System**
|
||||||
|
Which OS you used to reveal the bug.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
|
@ -0,0 +1,45 @@
|
||||||
|
*.log
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
awscpu
|
||||||
|
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.profile
|
||||||
|
|
||||||
|
# Test binary, build with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
*logs/
|
||||||
|
|
||||||
|
.vscode*
|
||||||
|
|
||||||
|
*.pb*
|
||||||
|
*.ava
|
||||||
|
|
||||||
|
db*
|
||||||
|
*cpu[0-9]*
|
||||||
|
*mem[0-9]*
|
||||||
|
*lock[0-9]*
|
||||||
|
*.profile
|
||||||
|
*.swp
|
||||||
|
*.aux
|
||||||
|
*.fdb*
|
||||||
|
*.fls
|
||||||
|
*.gz
|
||||||
|
*.pdf
|
||||||
|
|
||||||
|
.coverage
|
||||||
|
|
||||||
|
bin/
|
||||||
|
build/
|
||||||
|
|
||||||
|
*/mykey/staker.*
|
|
@ -0,0 +1,16 @@
|
||||||
|
dist: bionic
|
||||||
|
language: go
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- CODECOV_TOKEN="8c18c993-fc6e-4706-998b-01ddc7987804"
|
||||||
|
- GECKO_HOME=/go/src/github.com/ava-labs/gecko/
|
||||||
|
- COMMIT=${TRAVIS_COMMIT::8}
|
||||||
|
- DOCKERHUB_REPO=avaplatform/gecko
|
||||||
|
- secure: "L/A9+re0NEKP6EV6H9RcTGiDhX3WMvsiWrkRKDYKqnviqbjY30RK6EM4vvjrM4Lrw2QwsO3YKgnku3+zioE/TxEZFkpkbjNUXru0nYBrWAg1TKVsDXnYaIZkHUejfryST3E8N7F4Hx6zCtGEO0sEdUeKuT+MNUIuHezHooTgGzDjMogm70EWMFjQHc7VucTJu7dWU1RBPjovWQ0q9qflrtCpbrvXFIiihQQ1PQha1Q2C4wLakKuLbhhSafue90Mnyss0blaPHy/tyewcASJu4vsGTKRBn0DzttlkNTwuD6+nKrbmJY0ohunnkVFzYjrZAw1gyN+DCDb/lPbz4ZDItKPwrIUPEtL5xuUOrxUZPUh+0io3Q2d6rjaqkdGjd1KQXzbnW1mn0BxX3d3b2UpIqhBn9umYYjHBKnMuoRiTK33b7U9+LF3K84+tEvVDCPeHs/mw6Inp5jGRSravnM6yPQ6feGzogs4+3EMzZXxnkngKFKCsnd67Oe9xfV9amOU2aQAx4jaAwlPjEpBEkUa8YKx3lPznvmUk1QsNCUbLjdSl5JBaXojLJoiuPbj29hp4S5AXXgn+3Hvwk3ndcFCxi6/l1W9mjYSOtFqg3EAUdF4EgnA/ykQg9ZokkoKY0+qgOzG2bKOAYuCDWeGr7P1apToh00ccsQXL81nVPiq7uDw="
|
||||||
|
- secure: "zfTm7tJBYiPYrli76d4Ep6Lc2TJQ8Xv//+7OoqTA/aIf6YJDHe05f2GFTWAHG2iOIix/yjwHYwnhyIW66eWPb+Ujejnmh4eXlYZFufX9J5jUpDpbFu/+ybOLgE1Tmr0je0ycneSMe/NAaS74nWU1wnP34/cEE4sYL7TJyhwbeEtgz3cbSWwkpdvHFbXCjSOA196jdIYYUwsnqU9yycAG+2WUSk3DHHzzdtMrh/UOH2r1VFyp5US0zmbW90WkWX+o3TIlzZJgTUGQRNnWKq95Mrh1EQotxgL6CJ8NkfY4bVAGAhusPjdjscJsHxfY93WRMH64TzPYYp0zdibatH0ztyhnZPXVKqv+AIIVTEW+xWv5V18kTQAd1uBW103NFacbgXhIGWtbFcN9g1+ws29HROMclYs7ci6+72Qnq0eL55huqSyFx6+InhYwn+LfJmaBcGW4wx1umdp505M0obZ4ghlyn6b0pDYmqsu1XyBC3mjUTFbwlQmWE2Fize4L5o+DdH4ZDc9japF9ntxIMvO+b3nOicr7tplY2AGp61bB89o3dUAFlN5mDaEJotiAuFk5mo244rY1FjSzyGiKkA3M9TkTIbgcbN098hOJoMCYybH7yqiPwNnZiFvUuYjHuC5D1kIYBWuqqO0iVcbIZn0rV2jyzbVFlhFVk2clTZGhkrY="
|
||||||
|
before_install: .ci/before_install.sh
|
||||||
|
install: DOCKER_BUILDKIT=1 docker build --progress plain --ssh default -t $DOCKERHUB_REPO:$COMMIT .
|
||||||
|
script: docker run --rm -v "$PWD:$GECKO_HOME" $DOCKERHUB_REPO:$COMMIT bash "$GECKO_HOME/scripts/build_test.sh"
|
||||||
|
after_success: .ci/after_success.sh
|
|
@ -0,0 +1,17 @@
|
||||||
|
# syntax=docker/dockerfile:experimental
|
||||||
|
|
||||||
|
FROM golang:1.13.4-buster
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y libssl-dev libuv1-dev curl cmake
|
||||||
|
|
||||||
|
RUN mkdir -p /go/src/github.com/ava-labs
|
||||||
|
|
||||||
|
# Because downloading ethereum takes long it is done separately, so that the docker
|
||||||
|
# layer, when cached can be re-used
|
||||||
|
RUN go get -t -v github.com/ava-labs/go-ethereum
|
||||||
|
|
||||||
|
WORKDIR $GOPATH/src/github.com/ava-labs/
|
||||||
|
COPY . gecko
|
||||||
|
|
||||||
|
WORKDIR $GOPATH/src/github.com/ava-labs/gecko
|
||||||
|
RUN ./scripts/build.sh
|
|
@ -0,0 +1,29 @@
|
||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2020, Ava Labs, Inc.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,84 @@
|
||||||
|
# gecko
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
AVA is an incredibly lightweight protocol, so the minimum computer requirements are quite modest.
|
||||||
|
|
||||||
|
- Hardware: 2 GHz or faster CPU, 3 GB RAM, 250 MB hard disk.
|
||||||
|
- OS: Ubuntu >= 18.04 or Mac OS X >= Catalina.
|
||||||
|
- Software: [Go](https://golang.org/doc/install) version >= 1.13.X and set up [`$GOPATH`](https://github.com/golang/go/wiki/SettingGOPATH).
|
||||||
|
- Network: IPv4 or IPv6 network connection, with an open public port.
|
||||||
|
|
||||||
|
### Native Install
|
||||||
|
|
||||||
|
Ubuntu users need the following libraries:
|
||||||
|
|
||||||
|
* libssl-dev
|
||||||
|
* libuv1-dev
|
||||||
|
* cmake
|
||||||
|
* make
|
||||||
|
* curl
|
||||||
|
* g++
|
||||||
|
|
||||||
|
Install the libraries:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo apt-get install libssl-dev libuv1-dev cmake make curl g++
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Downloading Gecko Source Code
|
||||||
|
|
||||||
|
Clone the Gecko repository:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd $GOPATH
|
||||||
|
mkdir -p src/github.com/ava-labs
|
||||||
|
cd src/github.com/ava-labs
|
||||||
|
git clone https://github.com/ava-labs/gecko.git
|
||||||
|
cd gecko
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Building the Gecko Executable
|
||||||
|
|
||||||
|
Build Gecko using the build script:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./scripts/build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The Gecko binary, named `ava`, is in the `build` directory.
|
||||||
|
|
||||||
|
### Docker Install
|
||||||
|
|
||||||
|
- Make sure you have docker installed on your machine (so commands like `docker run` etc. are available).
|
||||||
|
- Build the docker image of latest gecko branch by `scripts/build_image.sh`.
|
||||||
|
- Check the built image by `docker image ls`, you should see some image tagged
|
||||||
|
`gecko-xxxxxxxx`, where `xxxxxxxx` is the commit id of the Gecko source it was built from.
|
||||||
|
- Test Gecko by `docker run -ti -p 9651:9651 gecko-xxxxxxxx /gecko/build/ava
|
||||||
|
--public-ip=127.0.0.1 --snow-sample-size=1 --snow-quorum-size=1 --staking-tls-enabled=false`. (For a production deployment,
|
||||||
|
you may want to extend the docker image with required credentials for
|
||||||
|
staking and TLS.)
|
||||||
|
|
||||||
|
## Running Gecko and Creating a Local Test Network
|
||||||
|
|
||||||
|
To create your own local test network, run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./build/ava --public-ip=127.0.0.1 --snow-sample-size=1 --snow-quorum-size=1 --staking-tls-enabled=false
|
||||||
|
```
|
||||||
|
|
||||||
|
This launches an AVA network with one node.
|
||||||
|
|
||||||
|
You should see some pretty ASCII art and log messages.
|
||||||
|
You may see a few warnings. These are OK.
|
||||||
|
|
||||||
|
You can use `Ctrl + C` to kill the node.
|
||||||
|
|
||||||
|
If you want to specify your log level. You should set `--log-level` to one of the following values, in decreasing order of logging.
|
||||||
|
* `--log-level=verbo`
|
||||||
|
* `--log-level=debug`
|
||||||
|
* `--log-level=info`
|
||||||
|
* `--log-level=warn`
|
||||||
|
* `--log-level=error`
|
||||||
|
* `--log-level=fatal`
|
||||||
|
* `--log-level=off`
|
|
@ -0,0 +1,27 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/ids"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetChainAliasesArgs are the arguments for Admin.GetChainAliases API call
|
||||||
|
type GetChainAliasesArgs struct{ ChainID string }
|
||||||
|
|
||||||
|
// GetChainAliasesReply are the arguments for Admin.GetChainAliases API call
|
||||||
|
type GetChainAliasesReply struct{ Aliases []string }
|
||||||
|
|
||||||
|
// GetChainAliases returns the aliases of the chain
|
||||||
|
// whose string representation is [args.ChainID]
|
||||||
|
func (service *Admin) GetChainAliases(r *http.Request, args *GetChainAliasesArgs, reply *GetChainAliasesReply) error {
|
||||||
|
ID, err := ids.FromString(args.ChainID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reply.Aliases = service.chainManager.Aliases(ID)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Peerable can return a group of peers
|
||||||
|
type Peerable interface{ Peers() []utils.IPDesc }
|
||||||
|
|
||||||
|
// Networking provides helper methods for tracking the current network state
|
||||||
|
type Networking struct{ peers Peerable }
|
||||||
|
|
||||||
|
// Peers returns the current peers
|
||||||
|
func (n *Networking) Peers() ([]string, error) {
|
||||||
|
ipDescs := n.peers.Peers()
|
||||||
|
ips := make([]string, len(ipDescs))
|
||||||
|
for i, ipDesc := range ipDescs {
|
||||||
|
ips[i] = ipDesc.String()
|
||||||
|
}
|
||||||
|
sort.Strings(ips)
|
||||||
|
return ips, nil
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"runtime/pprof"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errCPUProfilerRunning = errors.New("cpu profiler already running")
|
||||||
|
errCPUProfilerNotRunning = errors.New("cpu profiler doesn't exist")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Performance provides helper methods for measuring the current performance of
|
||||||
|
// the system
|
||||||
|
type Performance struct{ cpuProfileFile *os.File }
|
||||||
|
|
||||||
|
// StartCPUProfiler starts measuring the cpu utilization of this node
|
||||||
|
func (p *Performance) StartCPUProfiler(filename string) error {
|
||||||
|
if p.cpuProfileFile != nil {
|
||||||
|
return errCPUProfilerRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := pprof.StartCPUProfile(file); err != nil {
|
||||||
|
file.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
runtime.SetMutexProfileFraction(1)
|
||||||
|
|
||||||
|
p.cpuProfileFile = file
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopCPUProfiler stops measuring the cpu utilization of this node
|
||||||
|
func (p *Performance) StopCPUProfiler() error {
|
||||||
|
if p.cpuProfileFile == nil {
|
||||||
|
return errCPUProfilerNotRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
pprof.StopCPUProfile()
|
||||||
|
err := p.cpuProfileFile.Close()
|
||||||
|
p.cpuProfileFile = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemoryProfile dumps the current memory utilization of this node
|
||||||
|
func (p *Performance) MemoryProfile(filename string) error {
|
||||||
|
file, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
runtime.GC() // get up-to-date statistics
|
||||||
|
if err := pprof.WriteHeapProfile(file); err != nil {
|
||||||
|
file.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LockProfile dumps the current lock statistics of this node
|
||||||
|
func (p *Performance) LockProfile(filename string) error {
|
||||||
|
file, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
profile := pprof.Lookup("mutex")
|
||||||
|
if err := profile.WriteTo(file, 1); err != nil {
|
||||||
|
file.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return file.Close()
|
||||||
|
}
|
|
@ -0,0 +1,209 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/rpc/v2"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/api"
|
||||||
|
"github.com/ava-labs/gecko/chains"
|
||||||
|
"github.com/ava-labs/gecko/snow/engine/common"
|
||||||
|
"github.com/ava-labs/gecko/utils/logging"
|
||||||
|
|
||||||
|
cjson "github.com/ava-labs/gecko/utils/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Admin is the API service for node admin management
|
||||||
|
type Admin struct {
|
||||||
|
networkID uint32
|
||||||
|
log logging.Logger
|
||||||
|
networking Networking
|
||||||
|
performance Performance
|
||||||
|
chainManager chains.Manager
|
||||||
|
httpServer *api.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService returns a new admin API service
|
||||||
|
func NewService(networkID uint32, log logging.Logger, chainManager chains.Manager, peers Peerable, httpServer *api.Server) *common.HTTPHandler {
|
||||||
|
newServer := rpc.NewServer()
|
||||||
|
codec := cjson.NewCodec()
|
||||||
|
newServer.RegisterCodec(codec, "application/json")
|
||||||
|
newServer.RegisterCodec(codec, "application/json;charset=UTF-8")
|
||||||
|
newServer.RegisterService(&Admin{
|
||||||
|
networkID: networkID,
|
||||||
|
log: log,
|
||||||
|
chainManager: chainManager,
|
||||||
|
networking: Networking{
|
||||||
|
peers: peers,
|
||||||
|
},
|
||||||
|
httpServer: httpServer,
|
||||||
|
}, "admin")
|
||||||
|
return &common.HTTPHandler{Handler: newServer}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNetworkIDArgs are the arguments for calling GetNetworkID
|
||||||
|
type GetNetworkIDArgs struct{}
|
||||||
|
|
||||||
|
// GetNetworkIDReply are the results from calling GetNetworkID
|
||||||
|
type GetNetworkIDReply struct {
|
||||||
|
NetworkID cjson.Uint32 `json:"networkID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNetworkID returns the network ID this node is running on
|
||||||
|
func (service *Admin) GetNetworkID(r *http.Request, args *GetNetworkIDArgs, reply *GetNetworkIDReply) error {
|
||||||
|
service.log.Debug("Admin: GetNetworkID called")
|
||||||
|
|
||||||
|
reply.NetworkID = cjson.Uint32(service.networkID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockchainIDArgs are the arguments for calling GetBlockchainID
|
||||||
|
type GetBlockchainIDArgs struct {
|
||||||
|
Alias string `json:"alias"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockchainIDReply are the results from calling GetBlockchainID
|
||||||
|
type GetBlockchainIDReply struct {
|
||||||
|
BlockchainID string `json:"blockchainID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockchainID returns the blockchain ID that resolves the alias that was supplied
|
||||||
|
func (service *Admin) GetBlockchainID(r *http.Request, args *GetBlockchainIDArgs, reply *GetBlockchainIDReply) error {
|
||||||
|
service.log.Debug("Admin: GetBlockchainID called")
|
||||||
|
|
||||||
|
bID, err := service.chainManager.Lookup(args.Alias)
|
||||||
|
reply.BlockchainID = bID.String()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// PeersArgs are the arguments for calling Peers
|
||||||
|
type PeersArgs struct{}
|
||||||
|
|
||||||
|
// PeersReply are the results from calling Peers
|
||||||
|
type PeersReply struct {
|
||||||
|
Peers []string `json:"peers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peers returns the list of current validators
|
||||||
|
func (service *Admin) Peers(r *http.Request, args *PeersArgs, reply *PeersReply) error {
|
||||||
|
service.log.Debug("Admin: Peers called")
|
||||||
|
|
||||||
|
peers, err := service.networking.Peers()
|
||||||
|
reply.Peers = peers
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartCPUProfilerArgs are the arguments for calling StartCPUProfiler
|
||||||
|
type StartCPUProfilerArgs struct {
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartCPUProfilerReply are the results from calling StartCPUProfiler
|
||||||
|
type StartCPUProfilerReply struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartCPUProfiler starts a cpu profile writing to the specified file
|
||||||
|
func (service *Admin) StartCPUProfiler(r *http.Request, args *StartCPUProfilerArgs, reply *StartCPUProfilerReply) error {
|
||||||
|
service.log.Debug("Admin: StartCPUProfiler called with %s", args.Filename)
|
||||||
|
reply.Success = true
|
||||||
|
return service.performance.StartCPUProfiler(args.Filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopCPUProfilerArgs are the arguments for calling StopCPUProfiler
|
||||||
|
type StopCPUProfilerArgs struct{}
|
||||||
|
|
||||||
|
// StopCPUProfilerReply are the results from calling StopCPUProfiler
|
||||||
|
type StopCPUProfilerReply struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopCPUProfiler stops the cpu profile
|
||||||
|
func (service *Admin) StopCPUProfiler(r *http.Request, args *StopCPUProfilerArgs, reply *StopCPUProfilerReply) error {
|
||||||
|
service.log.Debug("Admin: StopCPUProfiler called")
|
||||||
|
reply.Success = true
|
||||||
|
return service.performance.StopCPUProfiler()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemoryProfileArgs are the arguments for calling MemoryProfile
|
||||||
|
type MemoryProfileArgs struct {
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemoryProfileReply are the results from calling MemoryProfile
|
||||||
|
type MemoryProfileReply struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemoryProfile runs a memory profile writing to the specified file
|
||||||
|
func (service *Admin) MemoryProfile(r *http.Request, args *MemoryProfileArgs, reply *MemoryProfileReply) error {
|
||||||
|
service.log.Debug("Admin: MemoryProfile called with %s", args.Filename)
|
||||||
|
reply.Success = true
|
||||||
|
return service.performance.MemoryProfile(args.Filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LockProfileArgs are the arguments for calling LockProfile
|
||||||
|
type LockProfileArgs struct {
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LockProfileReply are the results from calling LockProfile
|
||||||
|
type LockProfileReply struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LockProfile runs a mutex profile writing to the specified file
|
||||||
|
func (service *Admin) LockProfile(r *http.Request, args *LockProfileArgs, reply *LockProfileReply) error {
|
||||||
|
service.log.Debug("Admin: LockProfile called with %s", args.Filename)
|
||||||
|
reply.Success = true
|
||||||
|
return service.performance.LockProfile(args.Filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AliasArgs are the arguments for calling Alias
|
||||||
|
type AliasArgs struct {
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
Alias string `json:"alias"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AliasReply are the results from calling Alias
|
||||||
|
type AliasReply struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alias attempts to alias an HTTP endpoint to a new name
|
||||||
|
func (service *Admin) Alias(r *http.Request, args *AliasArgs, reply *AliasReply) error {
|
||||||
|
service.log.Debug("Admin: Alias called with URL: %s, Alias: %s", args.Endpoint, args.Alias)
|
||||||
|
reply.Success = true
|
||||||
|
return service.httpServer.AddAliasesWithReadLock(args.Endpoint, args.Alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AliasChainArgs are the arguments for calling AliasChain
|
||||||
|
type AliasChainArgs struct {
|
||||||
|
Chain string `json:"chain"`
|
||||||
|
Alias string `json:"alias"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AliasChainReply are the results from calling AliasChain
|
||||||
|
type AliasChainReply struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AliasChain attempts to alias a chain to a new name
|
||||||
|
func (service *Admin) AliasChain(_ *http.Request, args *AliasChainArgs, reply *AliasChainReply) error {
|
||||||
|
service.log.Debug("Admin: AliasChain called with Chain: %s, Alias: %s", args.Chain, args.Alias)
|
||||||
|
|
||||||
|
chainID, err := service.chainManager.Lookup(args.Chain)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := service.chainManager.Alias(chainID, args.Alias); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
reply.Success = true
|
||||||
|
return service.httpServer.AddAliasesWithReadLock("bc/"+chainID.String(), "bc/"+args.Alias)
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package ipcs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"nanomsg.org/go/mangos/v2"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/ids"
|
||||||
|
"github.com/ava-labs/gecko/utils/formatting"
|
||||||
|
"github.com/ava-labs/gecko/utils/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ChainIPC a struct which holds IPC socket information
|
||||||
|
type ChainIPC struct {
|
||||||
|
log logging.Logger
|
||||||
|
socket mangos.Socket
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept delivers a message to the ChainIPC
|
||||||
|
func (cipc *ChainIPC) Accept(chainID, containerID ids.ID, container []byte) error {
|
||||||
|
err := cipc.socket.Send(container)
|
||||||
|
if err != nil {
|
||||||
|
cipc.log.Error("%s while trying to send:\n%s", err, formatting.DumpBytes{Bytes: container})
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop halts the ChainIPC event loop
|
||||||
|
func (cipc *ChainIPC) Stop() error {
|
||||||
|
cipc.log.Info("closing Chain IPC")
|
||||||
|
return cipc.socket.Close()
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package ipcs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"nanomsg.org/go/mangos/v2/protocol/pub"
|
||||||
|
|
||||||
|
_ "nanomsg.org/go/mangos/v2/transport/ipc" // registers the IPC transport
|
||||||
|
|
||||||
|
"github.com/gorilla/rpc/v2"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/api"
|
||||||
|
"github.com/ava-labs/gecko/chains"
|
||||||
|
"github.com/ava-labs/gecko/snow/engine/common"
|
||||||
|
"github.com/ava-labs/gecko/snow/triggers"
|
||||||
|
"github.com/ava-labs/gecko/utils/json"
|
||||||
|
"github.com/ava-labs/gecko/utils/logging"
|
||||||
|
"github.com/ava-labs/gecko/utils/wrappers"
|
||||||
|
)
|
||||||
|
|
||||||
|
const baseURL = "ipc:///tmp/"
|
||||||
|
|
||||||
|
// IPCs maintains the IPCs
|
||||||
|
type IPCs struct {
|
||||||
|
log logging.Logger
|
||||||
|
chainManager chains.Manager
|
||||||
|
httpServer *api.Server
|
||||||
|
events *triggers.EventDispatcher
|
||||||
|
chains map[[32]byte]*ChainIPC
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService returns a new IPCs API service
|
||||||
|
func NewService(log logging.Logger, chainManager chains.Manager, events *triggers.EventDispatcher, httpServer *api.Server) *common.HTTPHandler {
|
||||||
|
newServer := rpc.NewServer()
|
||||||
|
codec := json.NewCodec()
|
||||||
|
newServer.RegisterCodec(codec, "application/json")
|
||||||
|
newServer.RegisterCodec(codec, "application/json;charset=UTF-8")
|
||||||
|
newServer.RegisterService(&IPCs{
|
||||||
|
log: log,
|
||||||
|
chainManager: chainManager,
|
||||||
|
httpServer: httpServer,
|
||||||
|
events: events,
|
||||||
|
chains: map[[32]byte]*ChainIPC{},
|
||||||
|
}, "ipcs")
|
||||||
|
return &common.HTTPHandler{Handler: newServer}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublishBlockchainArgs are the arguments for calling PublishBlockchain
|
||||||
|
type PublishBlockchainArgs struct {
|
||||||
|
BlockchainID string `json:"blockchainID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublishBlockchainReply are the results from calling PublishBlockchain
|
||||||
|
type PublishBlockchainReply struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublishBlockchain publishes the finalized accepted transactions from the blockchainID over the IPC
|
||||||
|
func (ipc *IPCs) PublishBlockchain(r *http.Request, args *PublishBlockchainArgs, reply *PublishBlockchainReply) error {
|
||||||
|
chainID, err := ipc.chainManager.Lookup(args.BlockchainID)
|
||||||
|
if err != nil {
|
||||||
|
ipc.log.Error("unknown blockchainID: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
chainIDKey := chainID.Key()
|
||||||
|
chainIDStr := chainID.String()
|
||||||
|
url := baseURL + chainIDStr + ".ipc"
|
||||||
|
|
||||||
|
reply.URL = url
|
||||||
|
|
||||||
|
if _, ok := ipc.chains[chainIDKey]; ok {
|
||||||
|
ipc.log.Info("returning existing blockchainID %s", chainIDStr)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sock, err := pub.NewSocket()
|
||||||
|
if err != nil {
|
||||||
|
ipc.log.Error("can't get new pub socket: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = sock.Listen(url); err != nil {
|
||||||
|
ipc.log.Error("can't listen on pub socket: %s", err)
|
||||||
|
sock.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
chainIPC := &ChainIPC{
|
||||||
|
log: ipc.log,
|
||||||
|
socket: sock,
|
||||||
|
}
|
||||||
|
if err := ipc.events.RegisterChain(chainID, "ipc", chainIPC); err != nil {
|
||||||
|
ipc.log.Error("couldn't register event: %s", err)
|
||||||
|
sock.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ipc.chains[chainIDKey] = chainIPC
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnpublishBlockchainArgs are the arguments for calling UnpublishBlockchain
|
||||||
|
type UnpublishBlockchainArgs struct {
|
||||||
|
BlockchainID string `json:"blockchainID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnpublishBlockchainReply are the results from calling UnpublishBlockchain
|
||||||
|
type UnpublishBlockchainReply struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnpublishBlockchain closes publishing of a blockchainID
|
||||||
|
func (ipc *IPCs) UnpublishBlockchain(r *http.Request, args *UnpublishBlockchainArgs, reply *UnpublishBlockchainReply) error {
|
||||||
|
chainID, err := ipc.chainManager.Lookup(args.BlockchainID)
|
||||||
|
if err != nil {
|
||||||
|
ipc.log.Error("unknown blockchainID %s: %s", args.BlockchainID, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
chainIDKey := chainID.Key()
|
||||||
|
|
||||||
|
chain, ok := ipc.chains[chainIDKey]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("blockchainID not publishing: %s", chainID)
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := wrappers.Errs{}
|
||||||
|
errs.Add(
|
||||||
|
chain.Stop(),
|
||||||
|
ipc.events.DeregisterChain(chainID, "ipc"),
|
||||||
|
)
|
||||||
|
delete(ipc.chains, chainIDKey)
|
||||||
|
|
||||||
|
reply.Success = true
|
||||||
|
return errs.Err
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package keystore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ava-labs/gecko/database"
|
||||||
|
"github.com/ava-labs/gecko/ids"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BlockchainKeystore ...
|
||||||
|
type BlockchainKeystore struct {
|
||||||
|
blockchainID ids.ID
|
||||||
|
ks *Keystore
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDatabase ...
|
||||||
|
func (bks *BlockchainKeystore) GetDatabase(username, password string) (database.Database, error) {
|
||||||
|
return bks.ks.GetDatabase(bks.blockchainID, username, password)
|
||||||
|
}
|
|
@ -0,0 +1,308 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package keystore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gorilla/rpc/v2"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/database"
|
||||||
|
"github.com/ava-labs/gecko/database/encdb"
|
||||||
|
"github.com/ava-labs/gecko/database/prefixdb"
|
||||||
|
"github.com/ava-labs/gecko/ids"
|
||||||
|
"github.com/ava-labs/gecko/snow/engine/common"
|
||||||
|
"github.com/ava-labs/gecko/utils/formatting"
|
||||||
|
"github.com/ava-labs/gecko/utils/logging"
|
||||||
|
"github.com/ava-labs/gecko/vms/components/codec"
|
||||||
|
|
||||||
|
jsoncodec "github.com/ava-labs/gecko/utils/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errEmptyUsername = errors.New("username can't be the empty string")
|
||||||
|
)
|
||||||
|
|
||||||
|
// KeyValuePair ...
|
||||||
|
type KeyValuePair struct {
|
||||||
|
Key []byte `serialize:"true"`
|
||||||
|
Value []byte `serialize:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserDB describes the full content of a user
|
||||||
|
type UserDB struct {
|
||||||
|
User `serialize:"true"`
|
||||||
|
Data []KeyValuePair `serialize:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keystore is the RPC interface for keystore management
|
||||||
|
type Keystore struct {
|
||||||
|
lock sync.Mutex
|
||||||
|
log logging.Logger
|
||||||
|
|
||||||
|
codec codec.Codec
|
||||||
|
|
||||||
|
// Key: username
|
||||||
|
// Value: The user with that name
|
||||||
|
users map[string]*User
|
||||||
|
|
||||||
|
// Used to persist users and their data
|
||||||
|
userDB database.Database
|
||||||
|
bcDB database.Database
|
||||||
|
// BaseDB
|
||||||
|
// / \
|
||||||
|
// UserDB BlockchainDB
|
||||||
|
// / | \
|
||||||
|
// Usr Usr Usr
|
||||||
|
// / | \
|
||||||
|
// BID BID BID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the keystore
|
||||||
|
func (ks *Keystore) Initialize(log logging.Logger, db database.Database) {
|
||||||
|
ks.log = log
|
||||||
|
ks.codec = codec.NewDefault()
|
||||||
|
ks.users = make(map[string]*User)
|
||||||
|
ks.userDB = prefixdb.New([]byte("users"), db)
|
||||||
|
ks.bcDB = prefixdb.New([]byte("bcs"), db)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateHandler returns a new service object that can send requests to thisAPI.
|
||||||
|
func (ks *Keystore) CreateHandler() *common.HTTPHandler {
|
||||||
|
newServer := rpc.NewServer()
|
||||||
|
codec := jsoncodec.NewCodec()
|
||||||
|
newServer.RegisterCodec(codec, "application/json")
|
||||||
|
newServer.RegisterCodec(codec, "application/json;charset=UTF-8")
|
||||||
|
newServer.RegisterService(ks, "keystore")
|
||||||
|
return &common.HTTPHandler{LockOptions: common.NoLock, Handler: newServer}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the user whose name is [username]
|
||||||
|
func (ks *Keystore) getUser(username string) (*User, error) {
|
||||||
|
// If the user is already in memory, return it
|
||||||
|
usr, exists := ks.users[username]
|
||||||
|
if exists {
|
||||||
|
return usr, nil
|
||||||
|
}
|
||||||
|
// The user is not in memory; try the database
|
||||||
|
usrBytes, err := ks.userDB.Get([]byte(username))
|
||||||
|
if err != nil { // Most likely bc user doesn't exist in database
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
usr = &User{}
|
||||||
|
return usr, ks.codec.Unmarshal(usrBytes, usr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUserArgs are arguments for passing into CreateUser requests
|
||||||
|
type CreateUserArgs struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUserReply is the response from calling CreateUser
|
||||||
|
type CreateUserReply struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUser creates an empty user with the provided username and password
|
||||||
|
func (ks *Keystore) CreateUser(_ *http.Request, args *CreateUserArgs, reply *CreateUserReply) error {
|
||||||
|
ks.lock.Lock()
|
||||||
|
defer ks.lock.Unlock()
|
||||||
|
|
||||||
|
ks.log.Verbo("CreateUser called with %s", args.Username)
|
||||||
|
|
||||||
|
if args.Username == "" {
|
||||||
|
return errEmptyUsername
|
||||||
|
}
|
||||||
|
if usr, err := ks.getUser(args.Username); err == nil || usr != nil {
|
||||||
|
return fmt.Errorf("user already exists: %s", args.Username)
|
||||||
|
}
|
||||||
|
|
||||||
|
usr := &User{}
|
||||||
|
if err := usr.Initialize(args.Password); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
usrBytes, err := ks.codec.Marshal(usr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ks.userDB.Put([]byte(args.Username), usrBytes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ks.users[args.Username] = usr
|
||||||
|
reply.Success = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListUsersArgs are the arguments to ListUsers
|
||||||
|
type ListUsersArgs struct{}
|
||||||
|
|
||||||
|
// ListUsersReply is the reply from ListUsers
|
||||||
|
type ListUsersReply struct {
|
||||||
|
Users []string `json:"users"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListUsers lists all the registered usernames
|
||||||
|
func (ks *Keystore) ListUsers(_ *http.Request, args *ListUsersArgs, reply *ListUsersReply) error {
|
||||||
|
ks.lock.Lock()
|
||||||
|
defer ks.lock.Unlock()
|
||||||
|
|
||||||
|
ks.log.Verbo("ListUsers called")
|
||||||
|
|
||||||
|
reply.Users = []string{}
|
||||||
|
|
||||||
|
it := ks.userDB.NewIterator()
|
||||||
|
defer it.Release()
|
||||||
|
for it.Next() {
|
||||||
|
reply.Users = append(reply.Users, string(it.Key()))
|
||||||
|
}
|
||||||
|
return it.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportUserArgs are the arguments to ExportUser
|
||||||
|
type ExportUserArgs struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportUserReply is the reply from ExportUser
|
||||||
|
type ExportUserReply struct {
|
||||||
|
User string `json:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportUser exports a serialized encoding of a user's information complete with encrypted database values
|
||||||
|
func (ks *Keystore) ExportUser(_ *http.Request, args *ExportUserArgs, reply *ExportUserReply) error {
|
||||||
|
ks.lock.Lock()
|
||||||
|
defer ks.lock.Unlock()
|
||||||
|
|
||||||
|
ks.log.Verbo("ExportUser called for %s", args.Username)
|
||||||
|
|
||||||
|
usr, err := ks.getUser(args.Username)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !usr.CheckPassword(args.Password) {
|
||||||
|
return fmt.Errorf("incorrect password for %s", args.Username)
|
||||||
|
}
|
||||||
|
|
||||||
|
userDB := prefixdb.New([]byte(args.Username), ks.bcDB)
|
||||||
|
|
||||||
|
userData := UserDB{
|
||||||
|
User: *usr,
|
||||||
|
}
|
||||||
|
|
||||||
|
it := userDB.NewIterator()
|
||||||
|
defer it.Release()
|
||||||
|
for it.Next() {
|
||||||
|
userData.Data = append(userData.Data, KeyValuePair{
|
||||||
|
Key: it.Key(),
|
||||||
|
Value: it.Value(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := it.Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := ks.codec.Marshal(&userData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cb58 := formatting.CB58{Bytes: b}
|
||||||
|
reply.User = cb58.String()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportUserArgs are arguments for ImportUser
|
||||||
|
type ImportUserArgs struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
User string `json:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportUserReply is the response for ImportUser
|
||||||
|
type ImportUserReply struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportUser imports a serialized encoding of a user's information complete with encrypted database values, integrity checks the password, and adds it to the database
|
||||||
|
func (ks *Keystore) ImportUser(r *http.Request, args *ImportUserArgs, reply *ImportUserReply) error {
|
||||||
|
ks.lock.Lock()
|
||||||
|
defer ks.lock.Unlock()
|
||||||
|
|
||||||
|
ks.log.Verbo("ImportUser called for %s", args.Username)
|
||||||
|
|
||||||
|
if usr, err := ks.getUser(args.Username); err == nil || usr != nil {
|
||||||
|
return fmt.Errorf("user already exists: %s", args.Username)
|
||||||
|
}
|
||||||
|
|
||||||
|
cb58 := formatting.CB58{}
|
||||||
|
if err := cb58.FromString(args.User); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
userData := UserDB{}
|
||||||
|
if err := ks.codec.Unmarshal(cb58.Bytes, &userData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
usrBytes, err := ks.codec.Marshal(&userData.User)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Should add batching to prevent creating a user without importing
|
||||||
|
// the account
|
||||||
|
if err := ks.userDB.Put([]byte(args.Username), usrBytes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ks.users[args.Username] = &userData.User
|
||||||
|
|
||||||
|
userDB := prefixdb.New([]byte(args.Username), ks.bcDB)
|
||||||
|
batch := userDB.NewBatch()
|
||||||
|
|
||||||
|
for _, kvp := range userData.Data {
|
||||||
|
batch.Put(kvp.Key, kvp.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
reply.Success = true
|
||||||
|
return batch.Write()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBlockchainKeyStore ...
|
||||||
|
func (ks *Keystore) NewBlockchainKeyStore(blockchainID ids.ID) *BlockchainKeystore {
|
||||||
|
return &BlockchainKeystore{
|
||||||
|
blockchainID: blockchainID,
|
||||||
|
ks: ks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDatabase ...
|
||||||
|
func (ks *Keystore) GetDatabase(bID ids.ID, username, password string) (database.Database, error) {
|
||||||
|
ks.lock.Lock()
|
||||||
|
defer ks.lock.Unlock()
|
||||||
|
|
||||||
|
usr, err := ks.getUser(username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !usr.CheckPassword(password) {
|
||||||
|
return nil, fmt.Errorf("incorrect password for user '%s'", username)
|
||||||
|
}
|
||||||
|
|
||||||
|
userDB := prefixdb.New([]byte(username), ks.bcDB)
|
||||||
|
bcDB := prefixdb.NewNested(bID.Bytes(), userDB)
|
||||||
|
encDB, err := encdb.New([]byte(password), bcDB)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return encDB, nil
|
||||||
|
}
|
|
@ -0,0 +1,202 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package keystore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/database/memdb"
|
||||||
|
"github.com/ava-labs/gecko/ids"
|
||||||
|
"github.com/ava-labs/gecko/utils/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServiceListNoUsers(t *testing.T) {
|
||||||
|
ks := Keystore{}
|
||||||
|
ks.Initialize(logging.NoLog{}, memdb.New())
|
||||||
|
|
||||||
|
reply := ListUsersReply{}
|
||||||
|
if err := ks.ListUsers(nil, &ListUsersArgs{}, &reply); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(reply.Users) != 0 {
|
||||||
|
t.Fatalf("No users should have been created yet")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServiceCreateUser(t *testing.T) {
|
||||||
|
ks := Keystore{}
|
||||||
|
ks.Initialize(logging.NoLog{}, memdb.New())
|
||||||
|
|
||||||
|
{
|
||||||
|
reply := CreateUserReply{}
|
||||||
|
if err := ks.CreateUser(nil, &CreateUserArgs{
|
||||||
|
Username: "bob",
|
||||||
|
Password: "launch",
|
||||||
|
}, &reply); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reply.Success {
|
||||||
|
t.Fatalf("User should have been created successfully")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
reply := ListUsersReply{}
|
||||||
|
if err := ks.ListUsers(nil, &ListUsersArgs{}, &reply); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(reply.Users) != 1 {
|
||||||
|
t.Fatalf("One user should have been created")
|
||||||
|
}
|
||||||
|
if user := reply.Users[0]; user != "bob" {
|
||||||
|
t.Fatalf("'bob' should have been a user that was created")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServiceCreateDuplicate(t *testing.T) {
|
||||||
|
ks := Keystore{}
|
||||||
|
ks.Initialize(logging.NoLog{}, memdb.New())
|
||||||
|
|
||||||
|
{
|
||||||
|
reply := CreateUserReply{}
|
||||||
|
if err := ks.CreateUser(nil, &CreateUserArgs{
|
||||||
|
Username: "bob",
|
||||||
|
Password: "launch",
|
||||||
|
}, &reply); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reply.Success {
|
||||||
|
t.Fatalf("User should have been created successfully")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
reply := CreateUserReply{}
|
||||||
|
if err := ks.CreateUser(nil, &CreateUserArgs{
|
||||||
|
Username: "bob",
|
||||||
|
Password: "launch!",
|
||||||
|
}, &reply); err == nil {
|
||||||
|
t.Fatalf("Should have errored due to the username already existing")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServiceCreateUserNoName(t *testing.T) {
|
||||||
|
ks := Keystore{}
|
||||||
|
ks.Initialize(logging.NoLog{}, memdb.New())
|
||||||
|
|
||||||
|
reply := CreateUserReply{}
|
||||||
|
if err := ks.CreateUser(nil, &CreateUserArgs{
|
||||||
|
Password: "launch",
|
||||||
|
}, &reply); err == nil {
|
||||||
|
t.Fatalf("Shouldn't have allowed empty username")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServiceUseBlockchainDB(t *testing.T) {
|
||||||
|
ks := Keystore{}
|
||||||
|
ks.Initialize(logging.NoLog{}, memdb.New())
|
||||||
|
|
||||||
|
{
|
||||||
|
reply := CreateUserReply{}
|
||||||
|
if err := ks.CreateUser(nil, &CreateUserArgs{
|
||||||
|
Username: "bob",
|
||||||
|
Password: "launch",
|
||||||
|
}, &reply); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reply.Success {
|
||||||
|
t.Fatalf("User should have been created successfully")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
db, err := ks.GetDatabase(ids.Empty, "bob", "launch")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := db.Put([]byte("hello"), []byte("world")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
db, err := ks.GetDatabase(ids.Empty, "bob", "launch")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if val, err := db.Get([]byte("hello")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if !bytes.Equal(val, []byte("world")) {
|
||||||
|
t.Fatalf("Should have read '%s' from the db", "world")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServiceExportImport(t *testing.T) {
|
||||||
|
ks := Keystore{}
|
||||||
|
ks.Initialize(logging.NoLog{}, memdb.New())
|
||||||
|
|
||||||
|
{
|
||||||
|
reply := CreateUserReply{}
|
||||||
|
if err := ks.CreateUser(nil, &CreateUserArgs{
|
||||||
|
Username: "bob",
|
||||||
|
Password: "launch",
|
||||||
|
}, &reply); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reply.Success {
|
||||||
|
t.Fatalf("User should have been created successfully")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
db, err := ks.GetDatabase(ids.Empty, "bob", "launch")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := db.Put([]byte("hello"), []byte("world")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exportReply := ExportUserReply{}
|
||||||
|
if err := ks.ExportUser(nil, &ExportUserArgs{
|
||||||
|
Username: "bob",
|
||||||
|
Password: "launch",
|
||||||
|
}, &exportReply); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newKS := Keystore{}
|
||||||
|
newKS.Initialize(logging.NoLog{}, memdb.New())
|
||||||
|
|
||||||
|
{
|
||||||
|
reply := ImportUserReply{}
|
||||||
|
if err := newKS.ImportUser(nil, &ImportUserArgs{
|
||||||
|
Username: "bob",
|
||||||
|
Password: "launch",
|
||||||
|
User: exportReply.User,
|
||||||
|
}, &reply); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reply.Success {
|
||||||
|
t.Fatalf("User should have been imported successfully")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
db, err := newKS.GetDatabase(ids.Empty, "bob", "launch")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if val, err := db.Get([]byte("hello")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if !bytes.Equal(val, []byte("world")) {
|
||||||
|
t.Fatalf("Should have read '%s' from the db", "world")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package keystore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/argon2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// User describes a user of the keystore
|
||||||
|
type User struct {
|
||||||
|
Password [32]byte `serialize:"true"` // The salted, hashed password
|
||||||
|
Salt [16]byte `serialize:"true"` // The salt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize ...
|
||||||
|
func (usr *User) Initialize(password string) error {
|
||||||
|
_, err := rand.Read(usr.Salt[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// pw is the salted, hashed password
|
||||||
|
pw := argon2.IDKey([]byte(password), usr.Salt[:], 1, 64*1024, 4, 32)
|
||||||
|
copy(usr.Password[:], pw[:32])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckPassword ...
|
||||||
|
func (usr *User) CheckPassword(password string) bool {
|
||||||
|
pw := argon2.IDKey([]byte(password), usr.Salt[:], 1, 64*1024, 4, 32)
|
||||||
|
return bytes.Equal(pw, usr.Password[:])
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package keystore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUser(t *testing.T) {
|
||||||
|
usr := User{}
|
||||||
|
if err := usr.Initialize("heytherepal"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !usr.CheckPassword("heytherepal") {
|
||||||
|
t.Fatalf("Should have verified the password")
|
||||||
|
}
|
||||||
|
if usr.CheckPassword("heytherepal!") {
|
||||||
|
t.Fatalf("Shouldn't have verified the password")
|
||||||
|
}
|
||||||
|
if usr.CheckPassword("") {
|
||||||
|
t.Fatalf("Shouldn't have verified the password")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ava-labs/gecko/snow/engine/common"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewService returns a new prometheus service
|
||||||
|
func NewService() (*prometheus.Registry, *common.HTTPHandler) {
|
||||||
|
registerer := prometheus.NewRegistry()
|
||||||
|
handler := promhttp.InstrumentMetricHandler(
|
||||||
|
registerer,
|
||||||
|
promhttp.HandlerFor(
|
||||||
|
registerer,
|
||||||
|
promhttp.HandlerOpts{},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return registerer, &common.HTTPHandler{LockOptions: common.NoLock, Handler: handler}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type middlewareHandler struct {
|
||||||
|
before, after func()
|
||||||
|
handler http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mh middlewareHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
if mh.before != nil {
|
||||||
|
mh.before()
|
||||||
|
}
|
||||||
|
if mh.after != nil {
|
||||||
|
defer mh.after()
|
||||||
|
}
|
||||||
|
mh.handler.ServeHTTP(writer, request)
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errUnknownBaseURL = errors.New("unknown base url")
|
||||||
|
errUnknownEndpoint = errors.New("unknown endpoint")
|
||||||
|
)
|
||||||
|
|
||||||
|
type router struct {
|
||||||
|
lock sync.RWMutex
|
||||||
|
router *mux.Router
|
||||||
|
|
||||||
|
routeLock sync.Mutex
|
||||||
|
reservedRoutes map[string]bool // Reserves routes so that there can't be alias that conflict
|
||||||
|
aliases map[string][]string // Maps a route to a set of reserved routes
|
||||||
|
routes map[string]map[string]http.Handler // Maps routes to a handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRouter() *router {
|
||||||
|
return &router{
|
||||||
|
router: mux.NewRouter(),
|
||||||
|
reservedRoutes: make(map[string]bool),
|
||||||
|
aliases: make(map[string][]string),
|
||||||
|
routes: make(map[string]map[string]http.Handler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *router) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
r.lock.RLock()
|
||||||
|
defer r.lock.RUnlock()
|
||||||
|
|
||||||
|
r.router.ServeHTTP(writer, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *router) GetHandler(base, endpoint string) (http.Handler, error) {
|
||||||
|
r.routeLock.Lock()
|
||||||
|
defer r.routeLock.Unlock()
|
||||||
|
|
||||||
|
urlBase, exists := r.routes[base]
|
||||||
|
if !exists {
|
||||||
|
return nil, errUnknownBaseURL
|
||||||
|
}
|
||||||
|
handler, exists := urlBase[endpoint]
|
||||||
|
if !exists {
|
||||||
|
return nil, errUnknownEndpoint
|
||||||
|
}
|
||||||
|
return handler, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *router) AddRouter(base, endpoint string, handler http.Handler) error {
|
||||||
|
r.lock.Lock()
|
||||||
|
defer r.lock.Unlock()
|
||||||
|
r.routeLock.Lock()
|
||||||
|
defer r.routeLock.Unlock()
|
||||||
|
|
||||||
|
return r.addRouter(base, endpoint, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *router) addRouter(base, endpoint string, handler http.Handler) error {
|
||||||
|
if r.reservedRoutes[base] {
|
||||||
|
return fmt.Errorf("couldn't route to %s as that route is either aliased or already maps to a handler", base)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.forceAddRouter(base, endpoint, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *router) forceAddRouter(base, endpoint string, handler http.Handler) error {
|
||||||
|
endpoints := r.routes[base]
|
||||||
|
if endpoints == nil {
|
||||||
|
endpoints = make(map[string]http.Handler)
|
||||||
|
}
|
||||||
|
url := base + endpoint
|
||||||
|
if _, exists := endpoints[endpoint]; exists {
|
||||||
|
return fmt.Errorf("failed to create endpoint as %s already exists", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoints[endpoint] = handler
|
||||||
|
r.routes[base] = endpoints
|
||||||
|
r.router.Handle(url, handler)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if aliases, exists := r.aliases[base]; exists {
|
||||||
|
for _, alias := range aliases {
|
||||||
|
if innerErr := r.forceAddRouter(alias, endpoint, handler); err == nil {
|
||||||
|
err = innerErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *router) AddAlias(base string, aliases ...string) error {
|
||||||
|
r.lock.Lock()
|
||||||
|
defer r.lock.Unlock()
|
||||||
|
r.routeLock.Lock()
|
||||||
|
defer r.routeLock.Unlock()
|
||||||
|
|
||||||
|
for _, alias := range aliases {
|
||||||
|
if r.reservedRoutes[alias] {
|
||||||
|
return fmt.Errorf("couldn't alias to %s as that route is either already aliased or already maps to a handler", alias)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, alias := range aliases {
|
||||||
|
r.reservedRoutes[alias] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
r.aliases[base] = append(r.aliases[base], aliases...)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if endpoints, exists := r.routes[base]; exists {
|
||||||
|
for endpoint, handler := range endpoints {
|
||||||
|
for _, alias := range aliases {
|
||||||
|
if innerErr := r.forceAddRouter(alias, endpoint, handler); err == nil {
|
||||||
|
err = innerErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testHandler struct{}
|
||||||
|
|
||||||
|
func (*testHandler) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {}
|
||||||
|
|
||||||
|
func TestAliasing(t *testing.T) {
|
||||||
|
r := newRouter()
|
||||||
|
|
||||||
|
if err := r.AddAlias("1", "2", "3"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := r.AddAlias("1", "4"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := r.AddAlias("5", "1"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := r.AddAlias("3", "6"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := r.AddAlias("7", "4"); err == nil {
|
||||||
|
t.Fatalf("Already reserved %s", "4")
|
||||||
|
}
|
||||||
|
|
||||||
|
handler1 := &testHandler{}
|
||||||
|
if err := r.AddRouter("2", "", handler1); err == nil {
|
||||||
|
t.Fatalf("Already reserved %s", "2")
|
||||||
|
}
|
||||||
|
if err := r.AddRouter("5", "", handler1); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if handler, exists := r.routes["5"][""]; !exists {
|
||||||
|
t.Fatalf("Should have added %s", "5")
|
||||||
|
} else if handler != handler1 {
|
||||||
|
t.Fatalf("Registered unknown handler")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.AddAlias("5", "7"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if handler, exists := r.routes["7"][""]; !exists {
|
||||||
|
t.Fatalf("Should have added %s", "7")
|
||||||
|
} else if handler != handler1 {
|
||||||
|
t.Fatalf("Registered unknown handler")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBlock(t *testing.T) {
|
||||||
|
r := newRouter()
|
||||||
|
|
||||||
|
if err := r.AddAlias("1", "1"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler1 := &testHandler{}
|
||||||
|
if err := r.AddRouter("1", "", handler1); err == nil {
|
||||||
|
t.Fatalf("Permanently locked %s", "1")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gorilla/handlers"
|
||||||
|
|
||||||
|
"github.com/rs/cors"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/snow"
|
||||||
|
"github.com/ava-labs/gecko/snow/engine/common"
|
||||||
|
"github.com/ava-labs/gecko/utils/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
const baseURL = "/ext"
|
||||||
|
|
||||||
|
var (
|
||||||
|
errUnknownLockOption = errors.New("invalid lock options")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server maintains the HTTP router
|
||||||
|
type Server struct {
|
||||||
|
log logging.Logger
|
||||||
|
factory logging.Factory
|
||||||
|
router *router
|
||||||
|
portURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize creates the API server at the provided port
|
||||||
|
func (s *Server) Initialize(log logging.Logger, factory logging.Factory, port uint16) {
|
||||||
|
s.log = log
|
||||||
|
s.factory = factory
|
||||||
|
s.portURL = fmt.Sprintf(":%d", port)
|
||||||
|
s.router = newRouter()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch starts the API server
|
||||||
|
func (s *Server) Dispatch() error {
|
||||||
|
handler := cors.Default().Handler(s.router)
|
||||||
|
return http.ListenAndServe(s.portURL, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DispatchTLS starts the API server with the provided TLS certificate
|
||||||
|
func (s *Server) DispatchTLS(certFile, keyFile string) error {
|
||||||
|
handler := cors.Default().Handler(s.router)
|
||||||
|
return http.ListenAndServeTLS(s.portURL, certFile, keyFile, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterChain registers the API endpoints associated with this chain That
|
||||||
|
// is, add <route, handler> pairs to server so that http calls can be made to
|
||||||
|
// the vm
|
||||||
|
func (s *Server) RegisterChain(ctx *snow.Context, vmIntf interface{}) {
|
||||||
|
vm, ok := vmIntf.(common.VM)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// all subroutes to a chain begin with "bc/<the chain's ID>"
|
||||||
|
defaultEndpoint := "bc/" + ctx.ChainID.String()
|
||||||
|
httpLogger, err := s.factory.MakeChain(ctx.ChainID, "http")
|
||||||
|
if err != nil {
|
||||||
|
s.log.Error("Failed to create new http logger: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.log.Verbo("About to add API endpoints for chain with ID %s", ctx.ChainID)
|
||||||
|
|
||||||
|
// Register each endpoint
|
||||||
|
for extension, service := range vm.CreateHandlers() {
|
||||||
|
// Validate that the route being added is valid
|
||||||
|
// e.g. "/foo" and "" are ok but "\n" is not
|
||||||
|
_, err := url.ParseRequestURI(extension)
|
||||||
|
if extension != "" && err != nil {
|
||||||
|
s.log.Warn("could not add route to chain's API handler because route is malformed: %s", extension)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s.log.Verbo("adding API endpoint: %s", defaultEndpoint+extension)
|
||||||
|
if err := s.AddRoute(service, &ctx.Lock, defaultEndpoint, extension, httpLogger); err != nil {
|
||||||
|
s.log.Error("error adding route: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRoute registers the appropriate endpoint for the vm given an endpoint
|
||||||
|
func (s *Server) AddRoute(handler *common.HTTPHandler, lock *sync.RWMutex, base, endpoint string, log logging.Logger) error {
|
||||||
|
url := fmt.Sprintf("%s/%s", baseURL, base)
|
||||||
|
s.log.Info("adding route %s%s", url, endpoint)
|
||||||
|
h := handlers.CombinedLoggingHandler(log, handler.Handler)
|
||||||
|
switch handler.LockOptions {
|
||||||
|
case common.WriteLock:
|
||||||
|
return s.router.AddRouter(url, endpoint, middlewareHandler{
|
||||||
|
before: lock.Lock,
|
||||||
|
after: lock.Unlock,
|
||||||
|
handler: h,
|
||||||
|
})
|
||||||
|
case common.ReadLock:
|
||||||
|
return s.router.AddRouter(url, endpoint, middlewareHandler{
|
||||||
|
before: lock.RLock,
|
||||||
|
after: lock.RUnlock,
|
||||||
|
handler: h,
|
||||||
|
})
|
||||||
|
case common.NoLock:
|
||||||
|
return s.router.AddRouter(url, endpoint, h)
|
||||||
|
default:
|
||||||
|
return errUnknownLockOption
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAliases registers aliases to the server
|
||||||
|
func (s *Server) AddAliases(endpoint string, aliases ...string) error {
|
||||||
|
url := fmt.Sprintf("%s/%s", baseURL, endpoint)
|
||||||
|
endpoints := make([]string, len(aliases))
|
||||||
|
for i, alias := range aliases {
|
||||||
|
endpoints[i] = fmt.Sprintf("%s/%s", baseURL, alias)
|
||||||
|
}
|
||||||
|
return s.router.AddAlias(url, endpoints...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAliasesWithReadLock registers aliases to the server assuming the http read
|
||||||
|
// lock is currently held.
|
||||||
|
func (s *Server) AddAliasesWithReadLock(endpoint string, aliases ...string) error {
|
||||||
|
// This is safe, as the read lock doesn't actually need to be held once the
|
||||||
|
// http handler is called. However, it is unlocked later, so this function
|
||||||
|
// must end with the lock held.
|
||||||
|
s.router.lock.RUnlock()
|
||||||
|
defer s.router.lock.RLock()
|
||||||
|
|
||||||
|
return s.AddAliases(endpoint, aliases...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call ...
|
||||||
|
func (s *Server) Call(
|
||||||
|
writer http.ResponseWriter,
|
||||||
|
method,
|
||||||
|
base,
|
||||||
|
endpoint string,
|
||||||
|
body io.Reader,
|
||||||
|
headers map[string]string,
|
||||||
|
) error {
|
||||||
|
url := fmt.Sprintf("%s/vm/%s", baseURL, base)
|
||||||
|
|
||||||
|
handler, err := s.router.GetHandler(url, endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", "*", body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for key, value := range headers {
|
||||||
|
req.Header.Set(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.ServeHTTP(writer, req)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/rpc/v2"
|
||||||
|
"github.com/gorilla/rpc/v2/json2"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/snow/engine/common"
|
||||||
|
"github.com/ava-labs/gecko/utils/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct{ called bool }
|
||||||
|
|
||||||
|
type Args struct{}
|
||||||
|
|
||||||
|
type Reply struct{}
|
||||||
|
|
||||||
|
func (s *Service) Call(_ *http.Request, args *Args, reply *Reply) error {
|
||||||
|
s.called = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCall(t *testing.T) {
|
||||||
|
s := Server{}
|
||||||
|
s.Initialize(logging.NoLog{}, logging.NoFactory{}, 8080)
|
||||||
|
|
||||||
|
serv := &Service{}
|
||||||
|
newServer := rpc.NewServer()
|
||||||
|
newServer.RegisterCodec(json2.NewCodec(), "application/json")
|
||||||
|
newServer.RegisterCodec(json2.NewCodec(), "application/json;charset=UTF-8")
|
||||||
|
newServer.RegisterService(serv, "test")
|
||||||
|
|
||||||
|
if err := s.AddRoute(&common.HTTPHandler{Handler: newServer}, new(sync.RWMutex), "vm/lol", "", logging.NoLog{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := json2.EncodeClientRequest("test.Call", &Args{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
writer := httptest.NewRecorder()
|
||||||
|
body := bytes.NewBuffer(buf)
|
||||||
|
headers := map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
s.Call(writer, "POST", "lol", "", body, headers)
|
||||||
|
|
||||||
|
if !serv.called {
|
||||||
|
t.Fatalf("Should have been called")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ava-labs/gecko/ids"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cacher acts as a best effort key value store
|
||||||
|
type Cacher interface {
|
||||||
|
// Put inserts an element into the cache. If spaced is required, elements will
|
||||||
|
// be evicted.
|
||||||
|
Put(key ids.ID, value interface{})
|
||||||
|
|
||||||
|
// Get returns the entry in the cache with the key specified, if no value
|
||||||
|
// exists, false is returned.
|
||||||
|
Get(key ids.ID) (interface{}, bool)
|
||||||
|
|
||||||
|
// Evict removes the specified entry from the cache
|
||||||
|
Evict(key ids.ID)
|
||||||
|
|
||||||
|
// Flush removes all entries from the cache
|
||||||
|
Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evictable allows the object to be notified when it is evicted
|
||||||
|
type Evictable interface {
|
||||||
|
ID() ids.ID
|
||||||
|
Evict()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deduplicator acts as a best effort deduplication service
|
||||||
|
type Deduplicator interface {
|
||||||
|
// Deduplicate returns either the provided value, or a previously provided
|
||||||
|
// value with the same ID that hasn't yet been evicted
|
||||||
|
Deduplicate(Evictable) Evictable
|
||||||
|
|
||||||
|
// Flush removes all entries from the cache
|
||||||
|
Flush()
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/ids"
|
||||||
|
)
|
||||||
|
|
||||||
|
type entry struct {
|
||||||
|
Key ids.ID
|
||||||
|
Value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LRU is a key value store with bounded size. If the size is attempted to be
|
||||||
|
// exceeded, then an element is removed from the cache before the insertion is
|
||||||
|
// done, based on evicting the least recently used value.
|
||||||
|
type LRU struct {
|
||||||
|
lock sync.Mutex
|
||||||
|
entryMap map[[32]byte]*list.Element
|
||||||
|
entryList *list.List
|
||||||
|
Size int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put implements the cache interface
|
||||||
|
func (c *LRU) Put(key ids.ID, value interface{}) {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
c.put(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implements the cache interface
|
||||||
|
func (c *LRU) Get(key ids.ID) (interface{}, bool) {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
return c.get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evict implements the cache interface
|
||||||
|
func (c *LRU) Evict(key ids.ID) {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
c.evict(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implements the cache interface
|
||||||
|
func (c *LRU) Flush() {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
c.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LRU) init() {
|
||||||
|
if c.entryMap == nil {
|
||||||
|
c.entryMap = make(map[[32]byte]*list.Element)
|
||||||
|
}
|
||||||
|
if c.entryList == nil {
|
||||||
|
c.entryList = list.New()
|
||||||
|
}
|
||||||
|
if c.Size <= 0 {
|
||||||
|
c.Size = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LRU) resize() {
|
||||||
|
for c.entryList.Len() > c.Size {
|
||||||
|
e := c.entryList.Front()
|
||||||
|
c.entryList.Remove(e)
|
||||||
|
|
||||||
|
val := e.Value.(*entry)
|
||||||
|
delete(c.entryMap, val.Key.Key())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LRU) put(key ids.ID, value interface{}) {
|
||||||
|
c.init()
|
||||||
|
c.resize()
|
||||||
|
|
||||||
|
if e, ok := c.entryMap[key.Key()]; !ok {
|
||||||
|
if c.entryList.Len() >= c.Size {
|
||||||
|
e = c.entryList.Front()
|
||||||
|
c.entryList.MoveToBack(e)
|
||||||
|
|
||||||
|
val := e.Value.(*entry)
|
||||||
|
delete(c.entryMap, val.Key.Key())
|
||||||
|
val.Key = key
|
||||||
|
val.Value = value
|
||||||
|
} else {
|
||||||
|
e = c.entryList.PushBack(&entry{
|
||||||
|
Key: key,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
c.entryMap[key.Key()] = e
|
||||||
|
} else {
|
||||||
|
c.entryList.MoveToBack(e)
|
||||||
|
|
||||||
|
val := e.Value.(*entry)
|
||||||
|
val.Value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LRU) get(key ids.ID) (interface{}, bool) {
|
||||||
|
c.init()
|
||||||
|
c.resize()
|
||||||
|
|
||||||
|
if e, ok := c.entryMap[key.Key()]; ok {
|
||||||
|
c.entryList.MoveToBack(e)
|
||||||
|
|
||||||
|
val := e.Value.(*entry)
|
||||||
|
return val.Value, true
|
||||||
|
}
|
||||||
|
return struct{}{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LRU) evict(key ids.ID) {
|
||||||
|
c.init()
|
||||||
|
c.resize()
|
||||||
|
|
||||||
|
keyBytes := key.Key()
|
||||||
|
if e, ok := c.entryMap[keyBytes]; ok {
|
||||||
|
c.entryList.Remove(e)
|
||||||
|
delete(c.entryMap, keyBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LRU) flush() {
|
||||||
|
c.init()
|
||||||
|
|
||||||
|
c.entryMap = make(map[[32]byte]*list.Element)
|
||||||
|
c.entryList = list.New()
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/ids"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLRU(t *testing.T) {
|
||||||
|
cache := LRU{Size: 1}
|
||||||
|
|
||||||
|
id1 := ids.NewID([32]byte{1})
|
||||||
|
if _, found := cache.Get(id1); found {
|
||||||
|
t.Fatalf("Retrieved value when none exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedValue1 := 1
|
||||||
|
cache.Put(id1, expectedValue1)
|
||||||
|
if value, found := cache.Get(id1); !found {
|
||||||
|
t.Fatalf("Failed to retrieve value when one exists")
|
||||||
|
} else if value != expectedValue1 {
|
||||||
|
t.Fatalf("Failed to retrieve correct value when one exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.Put(id1, expectedValue1)
|
||||||
|
if value, found := cache.Get(id1); !found {
|
||||||
|
t.Fatalf("Failed to retrieve value when one exists")
|
||||||
|
} else if value != expectedValue1 {
|
||||||
|
t.Fatalf("Failed to retrieve correct value when one exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.Put(id1, expectedValue1)
|
||||||
|
if value, found := cache.Get(id1); !found {
|
||||||
|
t.Fatalf("Failed to retrieve value when one exists")
|
||||||
|
} else if value != expectedValue1 {
|
||||||
|
t.Fatalf("Failed to retrieve correct value when one exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
id2 := ids.NewID([32]byte{2})
|
||||||
|
|
||||||
|
expectedValue2 := 2
|
||||||
|
cache.Put(id2, expectedValue2)
|
||||||
|
if _, found := cache.Get(id1); found {
|
||||||
|
t.Fatalf("Retrieved value when none exists")
|
||||||
|
}
|
||||||
|
if value, found := cache.Get(id2); !found {
|
||||||
|
t.Fatalf("Failed to retrieve value when one exists")
|
||||||
|
} else if value != expectedValue2 {
|
||||||
|
t.Fatalf("Failed to retrieve correct value when one exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLRUEviction(t *testing.T) {
|
||||||
|
cache := LRU{Size: 2}
|
||||||
|
|
||||||
|
id1 := ids.NewID([32]byte{1})
|
||||||
|
id2 := ids.NewID([32]byte{2})
|
||||||
|
id3 := ids.NewID([32]byte{3})
|
||||||
|
|
||||||
|
cache.Put(id1, 1)
|
||||||
|
cache.Put(id2, 2)
|
||||||
|
|
||||||
|
if val, found := cache.Get(id1); !found {
|
||||||
|
t.Fatalf("Failed to retrieve value when one exists")
|
||||||
|
} else if val != 1 {
|
||||||
|
t.Fatalf("Retrieved wrong value")
|
||||||
|
} else if val, found := cache.Get(id2); !found {
|
||||||
|
t.Fatalf("Failed to retrieve value when one exists")
|
||||||
|
} else if val != 2 {
|
||||||
|
t.Fatalf("Retrieved wrong value")
|
||||||
|
} else if _, found := cache.Get(id3); found {
|
||||||
|
t.Fatalf("Retrieve value when none exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.Put(id3, 3)
|
||||||
|
|
||||||
|
if _, found := cache.Get(id1); found {
|
||||||
|
t.Fatalf("Retrieve value when none exists")
|
||||||
|
} else if val, found := cache.Get(id2); !found {
|
||||||
|
t.Fatalf("Failed to retrieve value when one exists")
|
||||||
|
} else if val != 2 {
|
||||||
|
t.Fatalf("Retrieved wrong value")
|
||||||
|
} else if val, found := cache.Get(id3); !found {
|
||||||
|
t.Fatalf("Failed to retrieve value when one exists")
|
||||||
|
} else if val != 3 {
|
||||||
|
t.Fatalf("Retrieved wrong value")
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.Get(id2)
|
||||||
|
cache.Put(id1, 1)
|
||||||
|
|
||||||
|
if val, found := cache.Get(id1); !found {
|
||||||
|
t.Fatalf("Failed to retrieve value when one exists")
|
||||||
|
} else if val != 1 {
|
||||||
|
t.Fatalf("Retrieved wrong value")
|
||||||
|
} else if val, found := cache.Get(id2); !found {
|
||||||
|
t.Fatalf("Failed to retrieve value when one exists")
|
||||||
|
} else if val != 2 {
|
||||||
|
t.Fatalf("Retrieved wrong value")
|
||||||
|
} else if _, found := cache.Get(id3); found {
|
||||||
|
t.Fatalf("Retrieved value when none exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.Evict(id2)
|
||||||
|
cache.Put(id3, 3)
|
||||||
|
|
||||||
|
if val, found := cache.Get(id1); !found {
|
||||||
|
t.Fatalf("Failed to retrieve value when one exists")
|
||||||
|
} else if val != 1 {
|
||||||
|
t.Fatalf("Retrieved wrong value")
|
||||||
|
} else if _, found := cache.Get(id2); found {
|
||||||
|
t.Fatalf("Retrieved value when none exists")
|
||||||
|
} else if val, found := cache.Get(id3); !found {
|
||||||
|
t.Fatalf("Failed to retrieve value when one exists")
|
||||||
|
} else if val != 3 {
|
||||||
|
t.Fatalf("Retrieved wrong value")
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.Flush()
|
||||||
|
|
||||||
|
if _, found := cache.Get(id1); found {
|
||||||
|
t.Fatalf("Retrieved value when none exists")
|
||||||
|
} else if _, found := cache.Get(id2); found {
|
||||||
|
t.Fatalf("Retrieved value when none exists")
|
||||||
|
} else if _, found := cache.Get(id3); found {
|
||||||
|
t.Fatalf("Retrieved value when none exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLRUResize(t *testing.T) {
|
||||||
|
cache := LRU{Size: 2}
|
||||||
|
|
||||||
|
id1 := ids.NewID([32]byte{1})
|
||||||
|
id2 := ids.NewID([32]byte{2})
|
||||||
|
|
||||||
|
cache.Put(id1, 1)
|
||||||
|
cache.Put(id2, 2)
|
||||||
|
|
||||||
|
if val, found := cache.Get(id1); !found {
|
||||||
|
t.Fatalf("Failed to retrieve value when one exists")
|
||||||
|
} else if val != 1 {
|
||||||
|
t.Fatalf("Retrieved wrong value")
|
||||||
|
} else if val, found := cache.Get(id2); !found {
|
||||||
|
t.Fatalf("Failed to retrieve value when one exists")
|
||||||
|
} else if val != 2 {
|
||||||
|
t.Fatalf("Retrieved wrong value")
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.Size = 1
|
||||||
|
|
||||||
|
if _, found := cache.Get(id1); found {
|
||||||
|
t.Fatalf("Retrieve value when none exists")
|
||||||
|
} else if val, found := cache.Get(id2); !found {
|
||||||
|
t.Fatalf("Failed to retrieve value when one exists")
|
||||||
|
} else if val != 2 {
|
||||||
|
t.Fatalf("Retrieved wrong value")
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.Size = 0
|
||||||
|
|
||||||
|
if _, found := cache.Get(id1); found {
|
||||||
|
t.Fatalf("Retrieve value when none exists")
|
||||||
|
} else if val, found := cache.Get(id2); !found {
|
||||||
|
t.Fatalf("Failed to retrieve value when one exists")
|
||||||
|
} else if val != 2 {
|
||||||
|
t.Fatalf("Retrieved wrong value")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EvictableLRU is an LRU cache that notifies the objects when they are evicted.
|
||||||
|
type EvictableLRU struct {
|
||||||
|
lock sync.Mutex
|
||||||
|
entryMap map[[32]byte]*list.Element
|
||||||
|
entryList *list.List
|
||||||
|
Size int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deduplicate implements the Deduplicator interface
|
||||||
|
func (c *EvictableLRU) Deduplicate(value Evictable) Evictable {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
return c.deduplicate(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implements the Deduplicator interface
|
||||||
|
func (c *EvictableLRU) Flush() {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
c.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *EvictableLRU) init() {
|
||||||
|
if c.entryMap == nil {
|
||||||
|
c.entryMap = make(map[[32]byte]*list.Element)
|
||||||
|
}
|
||||||
|
if c.entryList == nil {
|
||||||
|
c.entryList = list.New()
|
||||||
|
}
|
||||||
|
if c.Size <= 0 {
|
||||||
|
c.Size = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *EvictableLRU) resize() {
|
||||||
|
for c.entryList.Len() > c.Size {
|
||||||
|
e := c.entryList.Front()
|
||||||
|
c.entryList.Remove(e)
|
||||||
|
|
||||||
|
val := e.Value.(Evictable)
|
||||||
|
delete(c.entryMap, val.ID().Key())
|
||||||
|
val.Evict()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *EvictableLRU) deduplicate(value Evictable) Evictable {
|
||||||
|
c.init()
|
||||||
|
c.resize()
|
||||||
|
|
||||||
|
key := value.ID().Key()
|
||||||
|
if e, ok := c.entryMap[key]; !ok {
|
||||||
|
if c.entryList.Len() >= c.Size {
|
||||||
|
e = c.entryList.Front()
|
||||||
|
c.entryList.MoveToBack(e)
|
||||||
|
|
||||||
|
val := e.Value.(Evictable)
|
||||||
|
delete(c.entryMap, val.ID().Key())
|
||||||
|
val.Evict()
|
||||||
|
|
||||||
|
e.Value = value
|
||||||
|
} else {
|
||||||
|
e = c.entryList.PushBack(value)
|
||||||
|
}
|
||||||
|
c.entryMap[key] = e
|
||||||
|
} else {
|
||||||
|
c.entryList.MoveToBack(e)
|
||||||
|
|
||||||
|
val := e.Value.(Evictable)
|
||||||
|
value = val
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *EvictableLRU) flush() {
|
||||||
|
c.init()
|
||||||
|
|
||||||
|
size := c.Size
|
||||||
|
c.Size = 0
|
||||||
|
c.resize()
|
||||||
|
c.Size = size
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/ids"
|
||||||
|
)
|
||||||
|
|
||||||
|
type evictable struct {
|
||||||
|
id ids.ID
|
||||||
|
evicted int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *evictable) ID() ids.ID { return e.id }
|
||||||
|
func (e *evictable) Evict() { e.evicted++ }
|
||||||
|
|
||||||
|
func TestEvictableLRU(t *testing.T) {
|
||||||
|
cache := EvictableLRU{}
|
||||||
|
|
||||||
|
expectedValue1 := &evictable{id: ids.NewID([32]byte{1})}
|
||||||
|
if returnedValue := cache.Deduplicate(expectedValue1).(*evictable); returnedValue != expectedValue1 {
|
||||||
|
t.Fatalf("Returned unknown value")
|
||||||
|
} else if expectedValue1.evicted != 0 {
|
||||||
|
t.Fatalf("Value was evicted unexpectedly")
|
||||||
|
} else if returnedValue := cache.Deduplicate(expectedValue1).(*evictable); returnedValue != expectedValue1 {
|
||||||
|
t.Fatalf("Returned unknown value")
|
||||||
|
} else if expectedValue1.evicted != 0 {
|
||||||
|
t.Fatalf("Value was evicted unexpectedly")
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedValue2 := &evictable{id: ids.NewID([32]byte{2})}
|
||||||
|
if returnedValue := cache.Deduplicate(expectedValue2).(*evictable); returnedValue != expectedValue2 {
|
||||||
|
t.Fatalf("Returned unknown value")
|
||||||
|
} else if expectedValue1.evicted != 1 {
|
||||||
|
t.Fatalf("Value should have been evicted")
|
||||||
|
} else if expectedValue2.evicted != 0 {
|
||||||
|
t.Fatalf("Value was evicted unexpectedly")
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.Size = 2
|
||||||
|
|
||||||
|
expectedValue3 := &evictable{id: ids.NewID([32]byte{2})}
|
||||||
|
if returnedValue := cache.Deduplicate(expectedValue3).(*evictable); returnedValue != expectedValue2 {
|
||||||
|
t.Fatalf("Returned unknown value")
|
||||||
|
} else if expectedValue1.evicted != 1 {
|
||||||
|
t.Fatalf("Value should have been evicted")
|
||||||
|
} else if expectedValue2.evicted != 0 {
|
||||||
|
t.Fatalf("Value was evicted unexpectedly")
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.Flush()
|
||||||
|
if expectedValue1.evicted != 1 {
|
||||||
|
t.Fatalf("Value should have been evicted")
|
||||||
|
} else if expectedValue2.evicted != 1 {
|
||||||
|
t.Fatalf("Value should have been evicted")
|
||||||
|
} else if expectedValue3.evicted != 0 {
|
||||||
|
t.Fatalf("Value was evicted unexpectedly")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package chains
|
||||||
|
|
||||||
|
import "github.com/ava-labs/gecko/snow/networking"
|
||||||
|
|
||||||
|
// Awaiter can await connections to be connected
|
||||||
|
type Awaiter interface {
|
||||||
|
AwaitConnections(awaiting *networking.AwaitingConnections)
|
||||||
|
}
|
|
@ -0,0 +1,524 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package chains
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/api"
|
||||||
|
"github.com/ava-labs/gecko/api/keystore"
|
||||||
|
"github.com/ava-labs/gecko/database"
|
||||||
|
"github.com/ava-labs/gecko/database/prefixdb"
|
||||||
|
"github.com/ava-labs/gecko/ids"
|
||||||
|
"github.com/ava-labs/gecko/snow"
|
||||||
|
"github.com/ava-labs/gecko/snow/consensus/snowball"
|
||||||
|
"github.com/ava-labs/gecko/snow/engine/avalanche"
|
||||||
|
"github.com/ava-labs/gecko/snow/engine/avalanche/state"
|
||||||
|
"github.com/ava-labs/gecko/snow/engine/common"
|
||||||
|
"github.com/ava-labs/gecko/snow/engine/common/queue"
|
||||||
|
"github.com/ava-labs/gecko/snow/networking"
|
||||||
|
"github.com/ava-labs/gecko/snow/networking/handler"
|
||||||
|
"github.com/ava-labs/gecko/snow/networking/router"
|
||||||
|
"github.com/ava-labs/gecko/snow/networking/sender"
|
||||||
|
"github.com/ava-labs/gecko/snow/networking/timeout"
|
||||||
|
"github.com/ava-labs/gecko/snow/triggers"
|
||||||
|
"github.com/ava-labs/gecko/snow/validators"
|
||||||
|
"github.com/ava-labs/gecko/utils/logging"
|
||||||
|
"github.com/ava-labs/gecko/vms"
|
||||||
|
|
||||||
|
avacon "github.com/ava-labs/gecko/snow/consensus/avalanche"
|
||||||
|
avaeng "github.com/ava-labs/gecko/snow/engine/avalanche"
|
||||||
|
|
||||||
|
smcon "github.com/ava-labs/gecko/snow/consensus/snowman"
|
||||||
|
smeng "github.com/ava-labs/gecko/snow/engine/snowman"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultChannelSize = 1000
|
||||||
|
requestTimeout = 2 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// Manager manages the chains running on this node.
|
||||||
|
// It can:
|
||||||
|
// * Create a chain
|
||||||
|
// * Add a registrant. When a chain is created, each registrant calls
|
||||||
|
// RegisterChain with the new chain as the argument.
|
||||||
|
// * Get the aliases associated with a given chain.
|
||||||
|
// * Get the ID of the chain associated with a given alias.
|
||||||
|
type Manager interface {
|
||||||
|
// Return the router this Manager is using to route consensus messages to chains
|
||||||
|
Router() router.Router
|
||||||
|
|
||||||
|
// Create a chain in the future
|
||||||
|
CreateChain(ChainParameters)
|
||||||
|
|
||||||
|
// Create a chain now
|
||||||
|
ForceCreateChain(ChainParameters)
|
||||||
|
|
||||||
|
// Add a registrant [r]. Every time a chain is
|
||||||
|
// created, [r].RegisterChain([new chain]) is called
|
||||||
|
AddRegistrant(Registrant)
|
||||||
|
|
||||||
|
// Given an alias, return the ID of the chain associated with that alias
|
||||||
|
Lookup(string) (ids.ID, error)
|
||||||
|
|
||||||
|
// Given an alias, return the ID of the VM associated with that alias
|
||||||
|
LookupVM(string) (ids.ID, error)
|
||||||
|
|
||||||
|
// Return the aliases associated with a chain
|
||||||
|
Aliases(ids.ID) []string
|
||||||
|
|
||||||
|
// Add an alias to a chain
|
||||||
|
Alias(ids.ID, string) error
|
||||||
|
|
||||||
|
Shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainParameters defines the chain being created
|
||||||
|
type ChainParameters struct {
|
||||||
|
ID ids.ID // The ID of the chain being created
|
||||||
|
SubnetID ids.ID // ID of the subnet that validates this chain
|
||||||
|
GenesisData []byte // The genesis data of this chain's ledger
|
||||||
|
VMAlias string // The ID of the vm this chain is running
|
||||||
|
FxAliases []string // The IDs of the feature extensions this chain is running
|
||||||
|
|
||||||
|
CustomBeacons validators.Set // Should only be set if the default beacons can't be used.
|
||||||
|
}
|
||||||
|
|
||||||
|
type manager struct {
|
||||||
|
// Note: The string representation of a chain's ID is also considered to be an alias of the chain
|
||||||
|
// That is, [chainID].String() is an alias for the chain, too
|
||||||
|
ids.Aliaser
|
||||||
|
|
||||||
|
log logging.Logger
|
||||||
|
logFactory logging.Factory
|
||||||
|
vmManager vms.Manager // Manage mappings from vm ID --> vm
|
||||||
|
decisionEvents *triggers.EventDispatcher
|
||||||
|
consensusEvents *triggers.EventDispatcher
|
||||||
|
db database.Database
|
||||||
|
chainRouter router.Router // Routes incoming messages to the appropriate chain
|
||||||
|
sender sender.ExternalSender // Sends consensus messages to other validators
|
||||||
|
timeoutManager *timeout.Manager // Manages request timeouts when sending messages to other validators
|
||||||
|
consensusParams avacon.Parameters // The consensus parameters (alpha, beta, etc.) for new chains
|
||||||
|
validators validators.Manager // Validators validating on this chain
|
||||||
|
registrants []Registrant // Those notified when a chain is created
|
||||||
|
nodeID ids.ShortID // The ID of this node
|
||||||
|
networkID uint32 // ID of the network this node is connected to
|
||||||
|
awaiter Awaiter // Waits for required connections before running bootstrapping
|
||||||
|
server *api.Server // Handles HTTP API calls
|
||||||
|
keystore *keystore.Keystore
|
||||||
|
|
||||||
|
unblocked bool
|
||||||
|
blockedChains []ChainParameters
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Manager where:
|
||||||
|
// <db> is this node's database
|
||||||
|
// <sender> sends messages to other validators
|
||||||
|
// <validators> validate this chain
|
||||||
|
// TODO: Make this function take less arguments
|
||||||
|
func New(
|
||||||
|
log logging.Logger,
|
||||||
|
logFactory logging.Factory,
|
||||||
|
vmManager vms.Manager,
|
||||||
|
decisionEvents *triggers.EventDispatcher,
|
||||||
|
consensusEvents *triggers.EventDispatcher,
|
||||||
|
db database.Database,
|
||||||
|
router router.Router,
|
||||||
|
sender sender.ExternalSender,
|
||||||
|
consensusParams avacon.Parameters,
|
||||||
|
validators validators.Manager,
|
||||||
|
nodeID ids.ShortID,
|
||||||
|
networkID uint32,
|
||||||
|
awaiter Awaiter,
|
||||||
|
server *api.Server,
|
||||||
|
keystore *keystore.Keystore,
|
||||||
|
) Manager {
|
||||||
|
timeoutManager := timeout.Manager{}
|
||||||
|
timeoutManager.Initialize(requestTimeout)
|
||||||
|
go log.RecoverAndPanic(timeoutManager.Dispatch)
|
||||||
|
|
||||||
|
router.Initialize(log, &timeoutManager)
|
||||||
|
|
||||||
|
m := &manager{
|
||||||
|
log: log,
|
||||||
|
logFactory: logFactory,
|
||||||
|
vmManager: vmManager,
|
||||||
|
decisionEvents: decisionEvents,
|
||||||
|
consensusEvents: consensusEvents,
|
||||||
|
db: db,
|
||||||
|
chainRouter: router,
|
||||||
|
sender: sender,
|
||||||
|
timeoutManager: &timeoutManager,
|
||||||
|
consensusParams: consensusParams,
|
||||||
|
validators: validators,
|
||||||
|
nodeID: nodeID,
|
||||||
|
networkID: networkID,
|
||||||
|
awaiter: awaiter,
|
||||||
|
server: server,
|
||||||
|
keystore: keystore,
|
||||||
|
}
|
||||||
|
m.Initialize()
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Router that this chain manager is using to route consensus messages to chains
|
||||||
|
func (m *manager) Router() router.Router { return m.chainRouter }
|
||||||
|
|
||||||
|
// Create a chain
|
||||||
|
func (m *manager) CreateChain(chain ChainParameters) {
|
||||||
|
if !m.unblocked {
|
||||||
|
m.blockedChains = append(m.blockedChains, chain)
|
||||||
|
} else {
|
||||||
|
m.ForceCreateChain(chain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a chain
|
||||||
|
func (m *manager) ForceCreateChain(chain ChainParameters) {
|
||||||
|
m.log.Info("creating chain:\n"+
|
||||||
|
" ID: %s\n"+
|
||||||
|
" VMID:%s",
|
||||||
|
chain.ID,
|
||||||
|
chain.VMAlias,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Assert that there isn't already a chain with an alias in [chain].Aliases
|
||||||
|
// (Recall that the string repr. of a chain's ID is also an alias for a chain)
|
||||||
|
if alias, isRepeat := m.isChainWithAlias(chain.ID.String()); isRepeat {
|
||||||
|
m.log.Error("there is already a chain with alias '%s'. Chain not created.", alias)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vmID, err := m.vmManager.Lookup(chain.VMAlias)
|
||||||
|
if err != nil {
|
||||||
|
m.log.Error("error while looking up VM: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a factory for the vm we want to use on our chain
|
||||||
|
vmFactory, err := m.vmManager.GetVMFactory(vmID)
|
||||||
|
if err != nil {
|
||||||
|
m.log.Error("error while getting vmFactory: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the chain
|
||||||
|
vm := vmFactory.New()
|
||||||
|
|
||||||
|
fxs := make([]*common.Fx, len(chain.FxAliases))
|
||||||
|
for i, fxAlias := range chain.FxAliases {
|
||||||
|
fxID, err := m.vmManager.Lookup(fxAlias)
|
||||||
|
if err != nil {
|
||||||
|
m.log.Error("error while looking up Fx: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a factory for the fx we want to use on our chain
|
||||||
|
fxFactory, err := m.vmManager.GetVMFactory(fxID)
|
||||||
|
if err != nil {
|
||||||
|
m.log.Error("error while getting fxFactory: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the fx
|
||||||
|
fxs[i] = &common.Fx{
|
||||||
|
ID: fxID,
|
||||||
|
Fx: fxFactory.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the log and context of the chain
|
||||||
|
chainLog, err := m.logFactory.MakeChain(chain.ID, "")
|
||||||
|
if err != nil {
|
||||||
|
m.log.Error("error while creating chain's log %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := &snow.Context{
|
||||||
|
NetworkID: m.networkID,
|
||||||
|
ChainID: chain.ID,
|
||||||
|
Log: chainLog,
|
||||||
|
DecisionDispatcher: m.decisionEvents,
|
||||||
|
ConsensusDispatcher: m.consensusEvents,
|
||||||
|
NodeID: m.nodeID,
|
||||||
|
HTTP: m.server,
|
||||||
|
Keystore: m.keystore.NewBlockchainKeyStore(chain.ID),
|
||||||
|
BCLookup: m,
|
||||||
|
}
|
||||||
|
consensusParams := m.consensusParams
|
||||||
|
if alias, err := m.PrimaryAlias(ctx.ChainID); err == nil {
|
||||||
|
consensusParams.Namespace = fmt.Sprintf("gecko_%s", alias)
|
||||||
|
} else {
|
||||||
|
consensusParams.Namespace = fmt.Sprintf("gecko_%s", ctx.ChainID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The validators of this blockchain
|
||||||
|
validators, ok := m.validators.GetValidatorSet(ids.Empty) // TODO: Change argument to chain.SubnetID
|
||||||
|
if !ok {
|
||||||
|
m.log.Error("couldn't get validator set of subnet with ID %s. The subnet may not exist", chain.SubnetID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
beacons := validators
|
||||||
|
if chain.CustomBeacons != nil {
|
||||||
|
beacons = chain.CustomBeacons
|
||||||
|
}
|
||||||
|
|
||||||
|
switch vm := vm.(type) {
|
||||||
|
case avalanche.DAGVM:
|
||||||
|
err := m.createAvalancheChain(
|
||||||
|
ctx,
|
||||||
|
chain.GenesisData,
|
||||||
|
validators,
|
||||||
|
beacons,
|
||||||
|
vm,
|
||||||
|
fxs,
|
||||||
|
consensusParams,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
m.log.Error("error while creating new avalanche vm %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case smeng.ChainVM:
|
||||||
|
err := m.createSnowmanChain(
|
||||||
|
ctx,
|
||||||
|
chain.GenesisData,
|
||||||
|
validators,
|
||||||
|
beacons,
|
||||||
|
vm,
|
||||||
|
fxs,
|
||||||
|
consensusParams.Parameters,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
m.log.Error("error while creating new snowman vm %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
m.log.Error("the vm should have type avalanche.DAGVM or snowman.ChainVM. Chain not created")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Associate the newly created chain with its default alias
|
||||||
|
m.log.AssertNoError(m.Alias(chain.ID, chain.ID.String()))
|
||||||
|
|
||||||
|
// Notify those that registered to be notified when a new chain is created
|
||||||
|
m.notifyRegistrants(ctx, vm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements Manager.AddRegistrant
|
||||||
|
func (m *manager) AddRegistrant(r Registrant) { m.registrants = append(m.registrants, r) }
|
||||||
|
|
||||||
|
func (m *manager) unblockChains() {
|
||||||
|
m.unblocked = true
|
||||||
|
blocked := m.blockedChains
|
||||||
|
m.blockedChains = nil
|
||||||
|
for _, chain := range blocked {
|
||||||
|
m.ForceCreateChain(chain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a DAG-based blockchain that uses Avalanche
|
||||||
|
func (m *manager) createAvalancheChain(
|
||||||
|
ctx *snow.Context,
|
||||||
|
genesisData []byte,
|
||||||
|
validators,
|
||||||
|
beacons validators.Set,
|
||||||
|
vm avalanche.DAGVM,
|
||||||
|
fxs []*common.Fx,
|
||||||
|
consensusParams avacon.Parameters,
|
||||||
|
) error {
|
||||||
|
ctx.Lock.Lock()
|
||||||
|
defer ctx.Lock.Unlock()
|
||||||
|
|
||||||
|
db := prefixdb.New(ctx.ChainID.Bytes(), m.db)
|
||||||
|
vmDB := prefixdb.New([]byte("vm"), db)
|
||||||
|
vertexDB := prefixdb.New([]byte("vertex"), db)
|
||||||
|
vertexBootstrappingDB := prefixdb.New([]byte("vertex_bootstrapping"), db)
|
||||||
|
txBootstrappingDB := prefixdb.New([]byte("tx_bootstrapping"), db)
|
||||||
|
|
||||||
|
vtxBlocker, err := queue.New(vertexBootstrappingDB)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
txBlocker, err := queue.New(txBootstrappingDB)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The channel through which a VM may send messages to the consensus engine
|
||||||
|
// VM uses this channel to notify engine that a block is ready to be made
|
||||||
|
msgChan := make(chan common.Message, defaultChannelSize)
|
||||||
|
|
||||||
|
if err := vm.Initialize(ctx, vmDB, genesisData, msgChan, fxs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles serialization/deserialization of vertices and also the
|
||||||
|
// persistence of vertices
|
||||||
|
vtxState := &state.Serializer{}
|
||||||
|
vtxState.Initialize(ctx, vm, vertexDB)
|
||||||
|
|
||||||
|
// Passes messages from the consensus engine to the network
|
||||||
|
sender := sender.Sender{}
|
||||||
|
sender.Initialize(ctx, m.sender, m.chainRouter, m.timeoutManager)
|
||||||
|
|
||||||
|
// The engine handles consensus
|
||||||
|
engine := avaeng.Transitive{
|
||||||
|
Config: avaeng.Config{
|
||||||
|
BootstrapConfig: avaeng.BootstrapConfig{
|
||||||
|
Config: common.Config{
|
||||||
|
Context: ctx,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.Initialize(avaeng.Config{
|
||||||
|
BootstrapConfig: avaeng.BootstrapConfig{
|
||||||
|
Config: common.Config{
|
||||||
|
Context: ctx,
|
||||||
|
Validators: validators,
|
||||||
|
Beacons: beacons,
|
||||||
|
Alpha: (beacons.Len() + 1) / 2,
|
||||||
|
Sender: &sender,
|
||||||
|
},
|
||||||
|
VtxBlocked: vtxBlocker,
|
||||||
|
TxBlocked: txBlocker,
|
||||||
|
State: vtxState,
|
||||||
|
VM: vm,
|
||||||
|
},
|
||||||
|
Params: consensusParams,
|
||||||
|
Consensus: &avacon.Topological{},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Asynchronously passes messages from the network to the consensus engine
|
||||||
|
handler := &handler.Handler{}
|
||||||
|
handler.Initialize(&engine, msgChan, defaultChannelSize)
|
||||||
|
|
||||||
|
// Allows messages to be routed to the new chain
|
||||||
|
m.chainRouter.AddChain(handler)
|
||||||
|
go ctx.Log.RecoverAndPanic(handler.Dispatch)
|
||||||
|
|
||||||
|
awaiting := &networking.AwaitingConnections{
|
||||||
|
Finish: func() {
|
||||||
|
ctx.Lock.Lock()
|
||||||
|
defer ctx.Lock.Unlock()
|
||||||
|
|
||||||
|
engine.Startup()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, vdr := range beacons.List() {
|
||||||
|
awaiting.Requested.Add(vdr.ID())
|
||||||
|
}
|
||||||
|
awaiting.NumRequired = (3*awaiting.Requested.Len() + 3) / 4 // 75% must be connected to
|
||||||
|
m.awaiter.AwaitConnections(awaiting)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a linear chain using the Snowman consensus engine
|
||||||
|
func (m *manager) createSnowmanChain(
|
||||||
|
ctx *snow.Context,
|
||||||
|
genesisData []byte,
|
||||||
|
validators,
|
||||||
|
beacons validators.Set,
|
||||||
|
vm smeng.ChainVM,
|
||||||
|
fxs []*common.Fx,
|
||||||
|
consensusParams snowball.Parameters,
|
||||||
|
) error {
|
||||||
|
ctx.Lock.Lock()
|
||||||
|
defer ctx.Lock.Unlock()
|
||||||
|
|
||||||
|
db := prefixdb.New(ctx.ChainID.Bytes(), m.db)
|
||||||
|
vmDB := prefixdb.New([]byte("vm"), db)
|
||||||
|
bootstrappingDB := prefixdb.New([]byte("bootstrapping"), db)
|
||||||
|
|
||||||
|
blocked, err := queue.New(bootstrappingDB)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The channel through which a VM may send messages to the consensus engine
|
||||||
|
// VM uses this channel to notify engine that a block is ready to be made
|
||||||
|
msgChan := make(chan common.Message, defaultChannelSize)
|
||||||
|
|
||||||
|
// Initialize the VM
|
||||||
|
if err := vm.Initialize(ctx, vmDB, genesisData, msgChan, fxs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Passes messages from the consensus engine to the network
|
||||||
|
sender := sender.Sender{}
|
||||||
|
sender.Initialize(ctx, m.sender, m.chainRouter, m.timeoutManager)
|
||||||
|
|
||||||
|
// The engine handles consensus
|
||||||
|
engine := smeng.Transitive{}
|
||||||
|
engine.Initialize(smeng.Config{
|
||||||
|
BootstrapConfig: smeng.BootstrapConfig{
|
||||||
|
Config: common.Config{
|
||||||
|
Context: ctx,
|
||||||
|
Validators: validators,
|
||||||
|
Beacons: beacons,
|
||||||
|
Alpha: (beacons.Len() + 1) / 2,
|
||||||
|
Sender: &sender,
|
||||||
|
},
|
||||||
|
Blocked: blocked,
|
||||||
|
VM: vm,
|
||||||
|
Bootstrapped: m.unblockChains,
|
||||||
|
},
|
||||||
|
Params: consensusParams,
|
||||||
|
Consensus: &smcon.Topological{},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Asynchronously passes messages from the network to the consensus engine
|
||||||
|
handler := &handler.Handler{}
|
||||||
|
handler.Initialize(&engine, msgChan, defaultChannelSize)
|
||||||
|
|
||||||
|
// Allow incoming messages to be routed to the new chain
|
||||||
|
m.chainRouter.AddChain(handler)
|
||||||
|
go ctx.Log.RecoverAndPanic(handler.Dispatch)
|
||||||
|
|
||||||
|
awaiting := &networking.AwaitingConnections{
|
||||||
|
Finish: func() {
|
||||||
|
ctx.Lock.Lock()
|
||||||
|
defer ctx.Lock.Unlock()
|
||||||
|
|
||||||
|
engine.Startup()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, vdr := range beacons.List() {
|
||||||
|
awaiting.Requested.Add(vdr.ID())
|
||||||
|
}
|
||||||
|
awaiting.NumRequired = (3*awaiting.Requested.Len() + 3) / 4 // 75% must be connected to
|
||||||
|
m.awaiter.AwaitConnections(awaiting)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown stops all the chains
|
||||||
|
func (m *manager) Shutdown() { m.chainRouter.Shutdown() }
|
||||||
|
|
||||||
|
// LookupVM returns the ID of the VM associated with an alias
|
||||||
|
func (m *manager) LookupVM(alias string) (ids.ID, error) { return m.vmManager.Lookup(alias) }
|
||||||
|
|
||||||
|
// Notify registrants [those who want to know about the creation of chains]
|
||||||
|
// that the specified chain has been created
|
||||||
|
func (m *manager) notifyRegistrants(ctx *snow.Context, vm interface{}) {
|
||||||
|
for _, registrant := range m.registrants {
|
||||||
|
registrant.RegisterChain(ctx, vm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns:
|
||||||
|
// 1) the alias that already exists, or the empty string if there is none
|
||||||
|
// 2) true iff there exists a chain such that the chain has an alias in [aliases]
|
||||||
|
func (m *manager) isChainWithAlias(aliases ...string) (string, bool) {
|
||||||
|
for _, alias := range aliases {
|
||||||
|
if _, err := m.Lookup(alias); err == nil {
|
||||||
|
return alias, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package chains
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ava-labs/gecko/snow"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Registrant can register the existence of a chain
|
||||||
|
type Registrant interface {
|
||||||
|
RegisterChain(ctx *snow.Context, vm interface{})
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
// For ease of implementation, our database's interface matches Ethereum's
|
||||||
|
// database implementation. This was to allow use to use Geth code as is for the
|
||||||
|
// EVM chain.
|
||||||
|
|
||||||
|
package database
|
||||||
|
|
||||||
|
// Batch is a write-only database that commits changes to its host database
|
||||||
|
// when Write is called. A batch cannot be used concurrently.
|
||||||
|
type Batch interface {
|
||||||
|
KeyValueWriter
|
||||||
|
|
||||||
|
// ValueSize retrieves the amount of data queued up for writing.
|
||||||
|
ValueSize() int
|
||||||
|
|
||||||
|
// Write flushes any accumulated data to disk.
|
||||||
|
Write() error
|
||||||
|
|
||||||
|
// Reset resets the batch for reuse.
|
||||||
|
Reset()
|
||||||
|
|
||||||
|
// Replay replays the batch contents.
|
||||||
|
Replay(w KeyValueWriter) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batcher wraps the NewBatch method of a backing data store.
|
||||||
|
type Batcher interface {
|
||||||
|
// NewBatch creates a write-only database that buffers changes to its host db
|
||||||
|
// until a final write is called.
|
||||||
|
NewBatch() Batch
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
// For ease of implementation, our database's interface matches Ethereum's
|
||||||
|
// database implementation. This was to allow use to use Geth code as is for the
|
||||||
|
// EVM chain.
|
||||||
|
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KeyValueReader wraps the Has and Get method of a backing data store.
|
||||||
|
type KeyValueReader interface {
|
||||||
|
// Has retrieves if a key is present in the key-value data store.
|
||||||
|
Has(key []byte) (bool, error)
|
||||||
|
|
||||||
|
// Get retrieves the given key if it's present in the key-value data store.
|
||||||
|
Get(key []byte) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyValueWriter wraps the Put method of a backing data store.
|
||||||
|
type KeyValueWriter interface {
|
||||||
|
// Put inserts the given value into the key-value data store.
|
||||||
|
Put(key []byte, value []byte) error
|
||||||
|
|
||||||
|
// Delete removes the key from the key-value data store.
|
||||||
|
Delete(key []byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stater wraps the Stat method of a backing data store.
|
||||||
|
type Stater interface {
|
||||||
|
// Stat returns a particular internal stat of the database.
|
||||||
|
Stat(property string) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compacter wraps the Compact method of a backing data store.
|
||||||
|
type Compacter interface {
|
||||||
|
// Compact the underlying DB for the given key range.
|
||||||
|
// Specifically, deleted and overwritten versions are discarded,
|
||||||
|
// and the data is rearranged to reduce the cost of operations
|
||||||
|
// needed to access the data. This operation should typically only
|
||||||
|
// be invoked by users who understand the underlying implementation.
|
||||||
|
//
|
||||||
|
// A nil start is treated as a key before all keys in the DB.
|
||||||
|
// And a nil limit is treated as a key after all keys in the DB.
|
||||||
|
// Therefore if both are nil then it will compact entire DB.
|
||||||
|
Compact(start []byte, limit []byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Database contains all the methods required to allow handling different
|
||||||
|
// key-value data stores backing the database.
|
||||||
|
type Database interface {
|
||||||
|
KeyValueReader
|
||||||
|
KeyValueWriter
|
||||||
|
Batcher
|
||||||
|
Iteratee
|
||||||
|
Stater
|
||||||
|
Compacter
|
||||||
|
io.Closer
|
||||||
|
}
|
|
@ -0,0 +1,283 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package encdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/database"
|
||||||
|
"github.com/ava-labs/gecko/database/nodb"
|
||||||
|
"github.com/ava-labs/gecko/utils/hashing"
|
||||||
|
"github.com/ava-labs/gecko/vms/components/codec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Database encrypts all values that are provided
|
||||||
|
type Database struct {
|
||||||
|
lock sync.RWMutex
|
||||||
|
codec codec.Codec
|
||||||
|
cipher cipher.AEAD
|
||||||
|
db database.Database
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new encrypted database
|
||||||
|
func New(password []byte, db database.Database) (*Database, error) {
|
||||||
|
h := hashing.ComputeHash256(password)
|
||||||
|
aead, err := chacha20poly1305.NewX(h)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Database{
|
||||||
|
codec: codec.NewDefault(),
|
||||||
|
cipher: aead,
|
||||||
|
db: db,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has implements the Database interface
|
||||||
|
func (db *Database) Has(key []byte) (bool, error) {
|
||||||
|
db.lock.RLock()
|
||||||
|
defer db.lock.RUnlock()
|
||||||
|
|
||||||
|
if db.db == nil {
|
||||||
|
return false, database.ErrClosed
|
||||||
|
}
|
||||||
|
return db.db.Has(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implements the Database interface
|
||||||
|
func (db *Database) Get(key []byte) ([]byte, error) {
|
||||||
|
db.lock.RLock()
|
||||||
|
defer db.lock.RUnlock()
|
||||||
|
|
||||||
|
if db.db == nil {
|
||||||
|
return nil, database.ErrClosed
|
||||||
|
}
|
||||||
|
encVal, err := db.db.Get(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return db.decrypt(encVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put implements the Database interface
|
||||||
|
func (db *Database) Put(key, value []byte) error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
|
if db.db == nil {
|
||||||
|
return database.ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
encValue, err := db.encrypt(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return db.db.Put(key, encValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete implements the Database interface
|
||||||
|
func (db *Database) Delete(key []byte) error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
|
if db.db == nil {
|
||||||
|
return database.ErrClosed
|
||||||
|
}
|
||||||
|
return db.db.Delete(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBatch implements the Database interface
|
||||||
|
func (db *Database) NewBatch() database.Batch {
|
||||||
|
return &batch{
|
||||||
|
Batch: db.db.NewBatch(),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIterator implements the Database interface
|
||||||
|
func (db *Database) NewIterator() database.Iterator { return db.NewIteratorWithStartAndPrefix(nil, nil) }
|
||||||
|
|
||||||
|
// NewIteratorWithStart implements the Database interface
|
||||||
|
func (db *Database) NewIteratorWithStart(start []byte) database.Iterator {
|
||||||
|
return db.NewIteratorWithStartAndPrefix(start, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIteratorWithPrefix implements the Database interface
|
||||||
|
func (db *Database) NewIteratorWithPrefix(prefix []byte) database.Iterator {
|
||||||
|
return db.NewIteratorWithStartAndPrefix(nil, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIteratorWithStartAndPrefix implements the Database interface
|
||||||
|
func (db *Database) NewIteratorWithStartAndPrefix(start, prefix []byte) database.Iterator {
|
||||||
|
db.lock.RLock()
|
||||||
|
defer db.lock.RUnlock()
|
||||||
|
|
||||||
|
if db.db == nil {
|
||||||
|
return &nodb.Iterator{Err: database.ErrClosed}
|
||||||
|
}
|
||||||
|
return &iterator{
|
||||||
|
Iterator: db.db.NewIteratorWithStartAndPrefix(start, prefix),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat implements the Database interface
|
||||||
|
func (db *Database) Stat(stat string) (string, error) {
|
||||||
|
db.lock.RLock()
|
||||||
|
defer db.lock.RUnlock()
|
||||||
|
|
||||||
|
if db.db == nil {
|
||||||
|
return "", database.ErrClosed
|
||||||
|
}
|
||||||
|
return db.db.Stat(stat)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compact implements the Database interface
|
||||||
|
func (db *Database) Compact(start, limit []byte) error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
|
if db.db == nil {
|
||||||
|
return database.ErrClosed
|
||||||
|
}
|
||||||
|
return db.db.Compact(start, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements the Database interface
|
||||||
|
func (db *Database) Close() error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
|
if db.db == nil {
|
||||||
|
return database.ErrClosed
|
||||||
|
}
|
||||||
|
db.db = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type keyValue struct {
|
||||||
|
key []byte
|
||||||
|
value []byte
|
||||||
|
delete bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type batch struct {
|
||||||
|
database.Batch
|
||||||
|
|
||||||
|
db *Database
|
||||||
|
writes []keyValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *batch) Put(key, value []byte) error {
|
||||||
|
b.writes = append(b.writes, keyValue{copyBytes(key), copyBytes(value), false})
|
||||||
|
encValue, err := b.db.encrypt(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return b.Batch.Put(key, encValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *batch) Delete(key []byte) error {
|
||||||
|
b.writes = append(b.writes, keyValue{copyBytes(key), nil, true})
|
||||||
|
return b.Batch.Delete(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *batch) Write() error {
|
||||||
|
b.db.lock.Lock()
|
||||||
|
defer b.db.lock.Unlock()
|
||||||
|
|
||||||
|
if b.db.db == nil {
|
||||||
|
return database.ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.Batch.Write()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset resets the batch for reuse.
|
||||||
|
func (b *batch) Reset() {
|
||||||
|
b.writes = b.writes[:0]
|
||||||
|
b.Batch.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replay replays the batch contents.
|
||||||
|
func (b *batch) Replay(w database.KeyValueWriter) error {
|
||||||
|
for _, keyvalue := range b.writes {
|
||||||
|
if keyvalue.delete {
|
||||||
|
if err := w.Delete(keyvalue.key); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if err := w.Put(keyvalue.key, keyvalue.value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type iterator struct {
|
||||||
|
database.Iterator
|
||||||
|
db *Database
|
||||||
|
|
||||||
|
val []byte
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *iterator) Next() bool {
|
||||||
|
next := it.Iterator.Next()
|
||||||
|
if next {
|
||||||
|
encVal := it.Iterator.Value()
|
||||||
|
val, err := it.db.decrypt(encVal)
|
||||||
|
if err != nil {
|
||||||
|
it.err = err
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
it.val = val
|
||||||
|
} else {
|
||||||
|
it.val = nil
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *iterator) Error() error {
|
||||||
|
if it.err != nil {
|
||||||
|
return it.err
|
||||||
|
}
|
||||||
|
return it.Iterator.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *iterator) Value() []byte { return it.val }
|
||||||
|
|
||||||
|
func copyBytes(bytes []byte) []byte {
|
||||||
|
copiedBytes := make([]byte, len(bytes))
|
||||||
|
copy(copiedBytes, bytes)
|
||||||
|
return copiedBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
type encryptedValue struct {
|
||||||
|
Ciphertext []byte `serialize:"true"`
|
||||||
|
Nonce []byte `serialize:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *Database) encrypt(plaintext []byte) ([]byte, error) {
|
||||||
|
nonce := make([]byte, chacha20poly1305.NonceSizeX)
|
||||||
|
if _, err := rand.Read(nonce); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ciphertext := db.cipher.Seal(nil, nonce, plaintext, nil)
|
||||||
|
return db.codec.Marshal(&encryptedValue{
|
||||||
|
Ciphertext: ciphertext,
|
||||||
|
Nonce: nonce,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *Database) decrypt(ciphertext []byte) ([]byte, error) {
|
||||||
|
val := encryptedValue{}
|
||||||
|
if err := db.codec.Unmarshal(ciphertext, &val); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return db.cipher.Open(nil, val.Nonce, val.Ciphertext, nil)
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package encdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/database"
|
||||||
|
"github.com/ava-labs/gecko/database/memdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInterface(t *testing.T) {
|
||||||
|
pw := "lol totally a secure password"
|
||||||
|
for _, test := range database.Tests {
|
||||||
|
unencryptedDB := memdb.New()
|
||||||
|
db, err := New([]byte(pw), unencryptedDB)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
test(t, db)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package database
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// common errors
|
||||||
|
var (
|
||||||
|
ErrClosed = errors.New("closed")
|
||||||
|
ErrNotFound = errors.New("not found")
|
||||||
|
)
|
|
@ -0,0 +1,62 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
// For ease of implementation, our database's interface matches Ethereum's
|
||||||
|
// database implementation. This was to allow use to use Geth code as is for the
|
||||||
|
// EVM chain.
|
||||||
|
|
||||||
|
package database
|
||||||
|
|
||||||
|
// Iterator iterates over a database's key/value pairs in ascending key order.
|
||||||
|
//
|
||||||
|
// When it encounters an error any seek will return false and will yield no key/
|
||||||
|
// value pairs. The error can be queried by calling the Error method. Calling
|
||||||
|
// Release is still necessary.
|
||||||
|
//
|
||||||
|
// An iterator must be released after use, but it is not necessary to read an
|
||||||
|
// iterator until exhaustion. An iterator is not safe for concurrent use, but it
|
||||||
|
// is safe to use multiple iterators concurrently.
|
||||||
|
type Iterator interface {
|
||||||
|
// Next moves the iterator to the next key/value pair. It returns whether the
|
||||||
|
// iterator is exhausted.
|
||||||
|
Next() bool
|
||||||
|
|
||||||
|
// Error returns any accumulated error. Exhausting all the key/value pairs
|
||||||
|
// is not considered to be an error.
|
||||||
|
Error() error
|
||||||
|
|
||||||
|
// Key returns the key of the current key/value pair, or nil if done. The caller
|
||||||
|
// should not modify the contents of the returned slice, and its contents may
|
||||||
|
// change on the next call to Next.
|
||||||
|
Key() []byte
|
||||||
|
|
||||||
|
// Value returns the value of the current key/value pair, or nil if done. The
|
||||||
|
// caller should not modify the contents of the returned slice, and its contents
|
||||||
|
// may change on the next call to Next.
|
||||||
|
Value() []byte
|
||||||
|
|
||||||
|
// Release releases associated resources. Release should always succeed and can
|
||||||
|
// be called multiple times without causing error.
|
||||||
|
Release()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iteratee wraps the NewIterator methods of a backing data store.
|
||||||
|
type Iteratee interface {
|
||||||
|
// NewIterator creates a binary-alphabetical iterator over the entire keyspace
|
||||||
|
// contained within the key-value database.
|
||||||
|
NewIterator() Iterator
|
||||||
|
|
||||||
|
// NewIteratorWithStart creates a binary-alphabetical iterator over a subset of
|
||||||
|
// database content starting at a particular initial key (or after, if it does
|
||||||
|
// not exist).
|
||||||
|
NewIteratorWithStart(start []byte) Iterator
|
||||||
|
|
||||||
|
// NewIteratorWithPrefix creates a binary-alphabetical iterator over a subset
|
||||||
|
// of database content with a particular key prefix.
|
||||||
|
NewIteratorWithPrefix(prefix []byte) Iterator
|
||||||
|
|
||||||
|
// NewIteratorWithStartAndPrefix creates a binary-alphabetical iterator over a
|
||||||
|
// subset of database content with a particular key prefix starting at a
|
||||||
|
// specified key.
|
||||||
|
NewIteratorWithStartAndPrefix(start, prefix []byte) Iterator
|
||||||
|
}
|
|
@ -0,0 +1,219 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package leveldb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/database"
|
||||||
|
"github.com/syndtr/goleveldb/leveldb"
|
||||||
|
"github.com/syndtr/goleveldb/leveldb/errors"
|
||||||
|
"github.com/syndtr/goleveldb/leveldb/filter"
|
||||||
|
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||||
|
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||||
|
"github.com/syndtr/goleveldb/leveldb/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// minBlockCacheSize is the minimum number of bytes to use for block caching
|
||||||
|
// in leveldb.
|
||||||
|
minBlockCacheSize = 8 * opt.MiB
|
||||||
|
|
||||||
|
// minWriteBufferSize is the minimum number of bytes to use for buffers in
|
||||||
|
// leveldb.
|
||||||
|
minWriteBufferSize = 8 * opt.MiB
|
||||||
|
|
||||||
|
// minHandleCap is the minimum number of files descriptors to cap levelDB to
|
||||||
|
// use
|
||||||
|
minHandleCap = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
// Database is a persistent key-value store. Apart from basic data storage
|
||||||
|
// functionality it also supports batch writes and iterating over the keyspace
|
||||||
|
// in binary-alphabetical order.
|
||||||
|
type Database struct{ *leveldb.DB }
|
||||||
|
|
||||||
|
// New returns a wrapped LevelDB object.
|
||||||
|
func New(file string, blockCacheSize, writeBufferSize, handleCap int) (*Database, error) {
|
||||||
|
// Enforce minimums
|
||||||
|
if blockCacheSize < minBlockCacheSize {
|
||||||
|
blockCacheSize = minBlockCacheSize
|
||||||
|
}
|
||||||
|
if writeBufferSize < minWriteBufferSize {
|
||||||
|
writeBufferSize = minWriteBufferSize
|
||||||
|
}
|
||||||
|
if handleCap < minHandleCap {
|
||||||
|
handleCap = minHandleCap
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the db and recover any potential corruptions
|
||||||
|
db, err := leveldb.OpenFile(file, &opt.Options{
|
||||||
|
OpenFilesCacheCapacity: handleCap,
|
||||||
|
BlockCacheCapacity: blockCacheSize,
|
||||||
|
// There are two buffers of size WriteBuffer used.
|
||||||
|
WriteBuffer: writeBufferSize / 2,
|
||||||
|
Filter: filter.NewBloomFilter(10),
|
||||||
|
})
|
||||||
|
if _, corrupted := err.(*errors.ErrCorrupted); corrupted {
|
||||||
|
db, err = leveldb.RecoverFile(file, nil)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Database{DB: db}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has returns if the key is set in the database
|
||||||
|
func (db *Database) Has(key []byte) (bool, error) {
|
||||||
|
has, err := db.DB.Has(key, nil)
|
||||||
|
return has, updateError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the value the key maps to in the database
|
||||||
|
func (db *Database) Get(key []byte) ([]byte, error) {
|
||||||
|
value, err := db.DB.Get(key, nil)
|
||||||
|
return value, updateError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put sets the value of the provided key to the provided value
|
||||||
|
func (db *Database) Put(key []byte, value []byte) error {
|
||||||
|
return updateError(db.DB.Put(key, value, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes the key from the database
|
||||||
|
func (db *Database) Delete(key []byte) error { return updateError(db.DB.Delete(key, nil)) }
|
||||||
|
|
||||||
|
// NewBatch creates a write/delete-only buffer that is atomically committed to
|
||||||
|
// the database when write is called
|
||||||
|
func (db *Database) NewBatch() database.Batch { return &batch{db: db.DB} }
|
||||||
|
|
||||||
|
// NewIterator creates a lexicographically ordered iterator over the database
|
||||||
|
func (db *Database) NewIterator() database.Iterator {
|
||||||
|
return &iter{db.DB.NewIterator(new(util.Range), nil)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIteratorWithStart creates a lexicographically ordered iterator over the
|
||||||
|
// database starting at the provided key
|
||||||
|
func (db *Database) NewIteratorWithStart(start []byte) database.Iterator {
|
||||||
|
return &iter{db.DB.NewIterator(&util.Range{Start: start}, nil)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIteratorWithPrefix creates a lexicographically ordered iterator over the
|
||||||
|
// database ignoring keys that do not start with the provided prefix
|
||||||
|
func (db *Database) NewIteratorWithPrefix(prefix []byte) database.Iterator {
|
||||||
|
return &iter{db.DB.NewIterator(util.BytesPrefix(prefix), nil)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIteratorWithStartAndPrefix creates a lexicographically ordered iterator
|
||||||
|
// over the database starting at start and ignoring keys that do not start with
|
||||||
|
// the provided prefix
|
||||||
|
func (db *Database) NewIteratorWithStartAndPrefix(start, prefix []byte) database.Iterator {
|
||||||
|
iterRange := util.BytesPrefix(prefix)
|
||||||
|
if bytes.Compare(start, prefix) == 1 {
|
||||||
|
iterRange.Start = start
|
||||||
|
}
|
||||||
|
return &iter{db.DB.NewIterator(iterRange, nil)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat returns a particular internal stat of the database.
|
||||||
|
func (db *Database) Stat(property string) (string, error) {
|
||||||
|
stat, err := db.DB.GetProperty(property)
|
||||||
|
return stat, updateError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This comment is basically copy pasted from the underlying levelDB library:
|
||||||
|
|
||||||
|
// Compact the underlying DB for the given key range.
|
||||||
|
// Specifically, deleted and overwritten versions are discarded,
|
||||||
|
// and the data is rearranged to reduce the cost of operations
|
||||||
|
// needed to access the data. This operation should typically only
|
||||||
|
// be invoked by users who understand the underlying implementation.
|
||||||
|
//
|
||||||
|
// A nil start is treated as a key before all keys in the DB.
|
||||||
|
// And a nil limit is treated as a key after all keys in the DB.
|
||||||
|
// Therefore if both are nil then it will compact entire DB.
|
||||||
|
func (db *Database) Compact(start []byte, limit []byte) error {
|
||||||
|
return updateError(db.DB.CompactRange(util.Range{Start: start, Limit: limit}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements the Database interface
|
||||||
|
func (db *Database) Close() error { return updateError(db.DB.Close()) }
|
||||||
|
|
||||||
|
// batch is a wrapper around a levelDB batch to contain sizes.
|
||||||
|
type batch struct {
|
||||||
|
leveldb.Batch
|
||||||
|
|
||||||
|
db *leveldb.DB
|
||||||
|
size int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put the value into the batch for later writing
|
||||||
|
func (b *batch) Put(key, value []byte) error {
|
||||||
|
b.Batch.Put(key, value)
|
||||||
|
b.size += len(value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the key during writing
|
||||||
|
func (b *batch) Delete(key []byte) error {
|
||||||
|
b.Batch.Delete(key)
|
||||||
|
b.size++
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValueSize retrieves the amount of data queued up for writing.
|
||||||
|
func (b *batch) ValueSize() int { return b.size }
|
||||||
|
|
||||||
|
// Write flushes any accumulated data to disk.
|
||||||
|
func (b *batch) Write() error { return updateError(b.db.Write(&b.Batch, nil)) }
|
||||||
|
|
||||||
|
// Reset resets the batch for reuse.
|
||||||
|
func (b *batch) Reset() {
|
||||||
|
b.Batch.Reset()
|
||||||
|
b.size = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replay the batch contents.
|
||||||
|
func (b *batch) Replay(w database.KeyValueWriter) error {
|
||||||
|
replay := &replayer{writer: w}
|
||||||
|
if err := b.Batch.Replay(replay); err != nil {
|
||||||
|
// Never actually returns an error, because Replay just returns nil
|
||||||
|
return updateError(err)
|
||||||
|
}
|
||||||
|
return updateError(replay.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type replayer struct {
|
||||||
|
writer database.KeyValueWriter
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayer) Put(key, value []byte) {
|
||||||
|
if r.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.err = r.writer.Put(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayer) Delete(key []byte) {
|
||||||
|
if r.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.err = r.writer.Delete(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
type iter struct{ iterator.Iterator }
|
||||||
|
|
||||||
|
func (i *iter) Error() error { return updateError(i.Iterator.Error()) }
|
||||||
|
|
||||||
|
func updateError(err error) error {
|
||||||
|
switch err {
|
||||||
|
case leveldb.ErrClosed:
|
||||||
|
return database.ErrClosed
|
||||||
|
case leveldb.ErrNotFound:
|
||||||
|
return database.ErrNotFound
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package leveldb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInterface(t *testing.T) {
|
||||||
|
for i, test := range database.Tests {
|
||||||
|
folder := fmt.Sprintf("db%d", i)
|
||||||
|
|
||||||
|
db, err := New(folder, 0, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("leveldb.New(%s, 0, 0) errored with %s", folder, err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(folder)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
test(t, db)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,258 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package memdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/database"
|
||||||
|
"github.com/ava-labs/gecko/database/nodb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultSize is the default initial size of the memory database
|
||||||
|
const DefaultSize = 1 << 10
|
||||||
|
|
||||||
|
// Database is an ephemeral key-value store that implements the Database
|
||||||
|
// interface.
|
||||||
|
type Database struct {
|
||||||
|
lock sync.RWMutex
|
||||||
|
db map[string][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a map with the Database interface methods implemented.
|
||||||
|
func New() *Database { return NewWithSize(DefaultSize) }
|
||||||
|
|
||||||
|
// NewWithSize returns a map pre-allocated to the provided size with the
|
||||||
|
// Database interface methods implemented.
|
||||||
|
func NewWithSize(size int) *Database { return &Database{db: make(map[string][]byte, size)} }
|
||||||
|
|
||||||
|
// Close implements the Database interface
|
||||||
|
func (db *Database) Close() error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
|
if db.db == nil {
|
||||||
|
return database.ErrClosed
|
||||||
|
}
|
||||||
|
db.db = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has implements the Database interface
|
||||||
|
func (db *Database) Has(key []byte) (bool, error) {
|
||||||
|
db.lock.RLock()
|
||||||
|
defer db.lock.RUnlock()
|
||||||
|
|
||||||
|
if db.db == nil {
|
||||||
|
return false, database.ErrClosed
|
||||||
|
}
|
||||||
|
_, ok := db.db[string(key)]
|
||||||
|
return ok, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implements the Database interface
|
||||||
|
func (db *Database) Get(key []byte) ([]byte, error) {
|
||||||
|
db.lock.RLock()
|
||||||
|
defer db.lock.RUnlock()
|
||||||
|
|
||||||
|
if db.db == nil {
|
||||||
|
return nil, database.ErrClosed
|
||||||
|
}
|
||||||
|
if entry, ok := db.db[string(key)]; ok {
|
||||||
|
return copyBytes(entry), nil
|
||||||
|
}
|
||||||
|
return nil, database.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put implements the Database interface
|
||||||
|
func (db *Database) Put(key []byte, value []byte) error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
|
if db.db == nil {
|
||||||
|
return database.ErrClosed
|
||||||
|
}
|
||||||
|
db.db[string(key)] = copyBytes(value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete implements the Database interface
|
||||||
|
func (db *Database) Delete(key []byte) error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
|
if db.db == nil {
|
||||||
|
return database.ErrClosed
|
||||||
|
}
|
||||||
|
delete(db.db, string(key))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBatch implements the Database interface
|
||||||
|
func (db *Database) NewBatch() database.Batch { return &batch{db: db} }
|
||||||
|
|
||||||
|
// NewIterator implements the Database interface
|
||||||
|
func (db *Database) NewIterator() database.Iterator { return db.NewIteratorWithStartAndPrefix(nil, nil) }
|
||||||
|
|
||||||
|
// NewIteratorWithStart implements the Database interface
|
||||||
|
func (db *Database) NewIteratorWithStart(start []byte) database.Iterator {
|
||||||
|
return db.NewIteratorWithStartAndPrefix(start, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIteratorWithPrefix implements the Database interface
|
||||||
|
func (db *Database) NewIteratorWithPrefix(prefix []byte) database.Iterator {
|
||||||
|
return db.NewIteratorWithStartAndPrefix(nil, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIteratorWithStartAndPrefix implements the Database interface
|
||||||
|
func (db *Database) NewIteratorWithStartAndPrefix(start, prefix []byte) database.Iterator {
|
||||||
|
db.lock.RLock()
|
||||||
|
defer db.lock.RUnlock()
|
||||||
|
|
||||||
|
if db.db == nil {
|
||||||
|
return &nodb.Iterator{Err: database.ErrClosed}
|
||||||
|
}
|
||||||
|
|
||||||
|
startString := string(start)
|
||||||
|
prefixString := string(prefix)
|
||||||
|
keys := make([]string, 0, len(db.db))
|
||||||
|
for key := range db.db {
|
||||||
|
if strings.HasPrefix(key, prefixString) && key >= startString {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(keys) // Keys need to be in sorted order
|
||||||
|
values := make([][]byte, 0, len(keys))
|
||||||
|
for _, key := range keys {
|
||||||
|
values = append(values, db.db[key])
|
||||||
|
}
|
||||||
|
return &iterator{
|
||||||
|
keys: keys,
|
||||||
|
values: values,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat implements the Database interface
|
||||||
|
func (db *Database) Stat(property string) (string, error) { return "", database.ErrNotFound }
|
||||||
|
|
||||||
|
// Compact implements the Database interface
|
||||||
|
func (db *Database) Compact(start []byte, limit []byte) error { return nil }
|
||||||
|
|
||||||
|
type keyValue struct {
|
||||||
|
key []byte
|
||||||
|
value []byte
|
||||||
|
delete bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type batch struct {
|
||||||
|
db *Database
|
||||||
|
writes []keyValue
|
||||||
|
size int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *batch) Put(key, value []byte) error {
|
||||||
|
b.writes = append(b.writes, keyValue{copyBytes(key), copyBytes(value), false})
|
||||||
|
b.size += len(value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *batch) Delete(key []byte) error {
|
||||||
|
b.writes = append(b.writes, keyValue{copyBytes(key), nil, true})
|
||||||
|
b.size++
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValueSize implements the Batch interface
|
||||||
|
func (b *batch) ValueSize() int { return b.size }
|
||||||
|
|
||||||
|
// Write implements the Batch interface
|
||||||
|
func (b *batch) Write() error {
|
||||||
|
b.db.lock.Lock()
|
||||||
|
defer b.db.lock.Unlock()
|
||||||
|
|
||||||
|
if b.db.db == nil {
|
||||||
|
return database.ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, kv := range b.writes {
|
||||||
|
key := string(kv.key)
|
||||||
|
if kv.delete {
|
||||||
|
delete(b.db.db, key)
|
||||||
|
} else {
|
||||||
|
b.db.db[key] = kv.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset implements the Batch interface
|
||||||
|
func (b *batch) Reset() {
|
||||||
|
b.writes = b.writes[:0]
|
||||||
|
b.size = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replay implements the Batch interface
|
||||||
|
func (b *batch) Replay(w database.KeyValueWriter) error {
|
||||||
|
for _, keyvalue := range b.writes {
|
||||||
|
if keyvalue.delete {
|
||||||
|
if err := w.Delete(keyvalue.key); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if err := w.Put(keyvalue.key, keyvalue.value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type iterator struct {
|
||||||
|
initialized bool
|
||||||
|
keys []string
|
||||||
|
values [][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next implements the Iterator interface
|
||||||
|
func (it *iterator) Next() bool {
|
||||||
|
// If the iterator was not yet initialized, do it now
|
||||||
|
if !it.initialized {
|
||||||
|
it.initialized = true
|
||||||
|
return len(it.keys) > 0
|
||||||
|
}
|
||||||
|
// Iterator already initialize, advance it
|
||||||
|
if len(it.keys) > 0 {
|
||||||
|
it.keys = it.keys[1:]
|
||||||
|
it.values = it.values[1:]
|
||||||
|
}
|
||||||
|
return len(it.keys) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements the Iterator interface
|
||||||
|
func (it *iterator) Error() error { return nil }
|
||||||
|
|
||||||
|
// Key implements the Iterator interface
|
||||||
|
func (it *iterator) Key() []byte {
|
||||||
|
if len(it.keys) > 0 {
|
||||||
|
return []byte(it.keys[0])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the Iterator interface
|
||||||
|
func (it *iterator) Value() []byte {
|
||||||
|
if len(it.values) > 0 {
|
||||||
|
return it.values[0]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release implements the Iterator interface
|
||||||
|
func (it *iterator) Release() { it.keys = nil; it.values = nil }
|
||||||
|
|
||||||
|
func copyBytes(bytes []byte) []byte {
|
||||||
|
copiedBytes := make([]byte, len(bytes))
|
||||||
|
copy(copiedBytes, bytes)
|
||||||
|
return copiedBytes
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package memdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInterface(t *testing.T) {
|
||||||
|
for _, test := range database.Tests {
|
||||||
|
test(t, New())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package mockdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errNoFunction = errors.New("user didn't specify what value(s) return")
|
||||||
|
|
||||||
|
// Database implements database.Database.
|
||||||
|
// This is a mock database meant to be used in tests.
|
||||||
|
// You specify the database's return value(s) for a given method call by
|
||||||
|
// assign value to the corresponding member.
|
||||||
|
// For example, to specify what should happen when Has is called,
|
||||||
|
// assign a value to OnHas.
|
||||||
|
// If no value is assigned to the corresponding member, the method returns an error or nil
|
||||||
|
// If you
|
||||||
|
type Database struct {
|
||||||
|
// Executed when Has is called
|
||||||
|
OnHas func([]byte) (bool, error)
|
||||||
|
OnGet func([]byte) ([]byte, error)
|
||||||
|
OnPut func([]byte) error
|
||||||
|
OnDelete func([]byte) error
|
||||||
|
OnNewBatch func() database.Batch
|
||||||
|
OnNewIterator func() database.Iterator
|
||||||
|
OnNewIteratorWithStart func([]byte) database.Iterator
|
||||||
|
OnNewIteratorWithPrefix func([]byte) database.Iterator
|
||||||
|
OnNewIteratorWithStartAndPrefix func([]byte, []byte) database.Iterator
|
||||||
|
OnStat func() (string, error)
|
||||||
|
OnCompact func([]byte, []byte) error
|
||||||
|
OnClose func() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has implements the database.Database interface
|
||||||
|
func (db *Database) Has(b []byte) (bool, error) {
|
||||||
|
if db.OnHas == nil {
|
||||||
|
return false, errNoFunction
|
||||||
|
}
|
||||||
|
return db.OnHas(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implements the database.Database interface
|
||||||
|
func (db *Database) Get(b []byte) ([]byte, error) {
|
||||||
|
if db.OnGet == nil {
|
||||||
|
return nil, errNoFunction
|
||||||
|
}
|
||||||
|
return db.OnGet(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put implements the database.Database interface
|
||||||
|
func (db *Database) Put(b []byte) error {
|
||||||
|
if db.OnPut == nil {
|
||||||
|
return errNoFunction
|
||||||
|
}
|
||||||
|
return db.OnPut(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete implements the database.Database interface
|
||||||
|
func (db *Database) Delete(b []byte) error {
|
||||||
|
if db.OnDelete == nil {
|
||||||
|
return errNoFunction
|
||||||
|
}
|
||||||
|
return db.OnDelete(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBatch implements the database.Database interface
|
||||||
|
func (db *Database) NewBatch() database.Batch {
|
||||||
|
if db.OnNewBatch == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return db.OnNewBatch()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIterator implements the database.Database interface
|
||||||
|
func (db *Database) NewIterator() database.Iterator {
|
||||||
|
if db.OnNewIterator == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return db.OnNewIterator()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIteratorWithStart implements the database.Database interface
|
||||||
|
func (db *Database) NewIteratorWithStart(start []byte) database.Iterator {
|
||||||
|
if db.OnNewIteratorWithStart == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return db.OnNewIteratorWithStart(start)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIteratorWithPrefix implements the database.Database interface
|
||||||
|
func (db *Database) NewIteratorWithPrefix(prefix []byte) database.Iterator {
|
||||||
|
if db.OnNewIteratorWithPrefix == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return db.OnNewIteratorWithPrefix(prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIteratorWithStartAndPrefix implements the database.Database interface
|
||||||
|
func (db *Database) NewIteratorWithStartAndPrefix(start, prefix []byte) database.Iterator {
|
||||||
|
if db.OnNewIteratorWithStartAndPrefix == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return db.OnNewIteratorWithStartAndPrefix(start, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat implements the database.Database interface
|
||||||
|
func (db *Database) Stat() (string, error) {
|
||||||
|
if db.OnStat == nil {
|
||||||
|
return "", errNoFunction
|
||||||
|
}
|
||||||
|
return db.OnStat()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compact implements the database.Database interface
|
||||||
|
func (db *Database) Compact(start []byte, limit []byte) error {
|
||||||
|
if db.OnCompact == nil {
|
||||||
|
return errNoFunction
|
||||||
|
}
|
||||||
|
return db.OnCompact(start, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements the database.Database interface
|
||||||
|
func (db *Database) Close() error {
|
||||||
|
if db.OnClose == nil {
|
||||||
|
return errNoFunction
|
||||||
|
}
|
||||||
|
return db.OnClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new mock database
|
||||||
|
func New() *Database {
|
||||||
|
return &Database{}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package mockdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Assert that when no members are assigned values, every method returns nil/error
|
||||||
|
func TestDefaultError(t *testing.T) {
|
||||||
|
db := New()
|
||||||
|
|
||||||
|
if err := db.Close(); err == nil {
|
||||||
|
t.Fatal("should have errored")
|
||||||
|
}
|
||||||
|
if _, err := db.Has([]byte{}); err == nil {
|
||||||
|
t.Fatal("should have errored")
|
||||||
|
}
|
||||||
|
if _, err := db.Get([]byte{}); err == nil {
|
||||||
|
t.Fatal("should have errored")
|
||||||
|
}
|
||||||
|
if err := db.Put([]byte{}); err == nil {
|
||||||
|
t.Fatal("should have errored")
|
||||||
|
}
|
||||||
|
if err := db.Delete([]byte{}); err == nil {
|
||||||
|
t.Fatal("should have errored")
|
||||||
|
}
|
||||||
|
if batch := db.NewBatch(); batch != nil {
|
||||||
|
t.Fatal("should have been nil")
|
||||||
|
}
|
||||||
|
if iterator := db.NewIterator(); iterator != nil {
|
||||||
|
t.Fatal("should have errored")
|
||||||
|
}
|
||||||
|
if iterator := db.NewIteratorWithPrefix([]byte{}); iterator != nil {
|
||||||
|
t.Fatal("should have errored")
|
||||||
|
}
|
||||||
|
if iterator := db.NewIteratorWithStart([]byte{}); iterator != nil {
|
||||||
|
t.Fatal("should have errored")
|
||||||
|
}
|
||||||
|
if iterator := db.NewIteratorWithStartAndPrefix([]byte{}, []byte{}); iterator != nil {
|
||||||
|
t.Fatal("should have errored")
|
||||||
|
}
|
||||||
|
if err := db.Compact([]byte{}, []byte{}); err == nil {
|
||||||
|
t.Fatal("should have errored")
|
||||||
|
}
|
||||||
|
if _, err := db.Stat(); err == nil {
|
||||||
|
t.Fatal("should have errored")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that mocking works for Get
|
||||||
|
func TestGet(t *testing.T) {
|
||||||
|
db := New()
|
||||||
|
|
||||||
|
// Mock Has()
|
||||||
|
db.OnHas = func(b []byte) (bool, error) {
|
||||||
|
if bytes.Equal(b, []byte{1, 2, 3}) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, errors.New("")
|
||||||
|
}
|
||||||
|
|
||||||
|
if has, err := db.Has([]byte{1, 2, 3}); err != nil {
|
||||||
|
t.Fatal("should not have errored")
|
||||||
|
} else if has != true {
|
||||||
|
t.Fatal("has should be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := db.Has([]byte{1, 2}); err == nil {
|
||||||
|
t.Fatal("should have have errored")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package nodb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ava-labs/gecko/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Database is a lightning fast key value store with probabilistic operations.
|
||||||
|
type Database struct{}
|
||||||
|
|
||||||
|
// Has returns false, nil
|
||||||
|
func (*Database) Has([]byte) (bool, error) { return false, database.ErrClosed }
|
||||||
|
|
||||||
|
// Get returns nil, error
|
||||||
|
func (*Database) Get([]byte) ([]byte, error) { return nil, database.ErrClosed }
|
||||||
|
|
||||||
|
// Put returns nil
|
||||||
|
func (*Database) Put(_ []byte, _ []byte) error { return database.ErrClosed }
|
||||||
|
|
||||||
|
// Delete returns nil
|
||||||
|
func (*Database) Delete([]byte) error { return database.ErrClosed }
|
||||||
|
|
||||||
|
// NewBatch returns a new batch
|
||||||
|
func (*Database) NewBatch() database.Batch { return &Batch{} }
|
||||||
|
|
||||||
|
// NewIterator returns a new empty iterator
|
||||||
|
func (*Database) NewIterator() database.Iterator { return &Iterator{} }
|
||||||
|
|
||||||
|
// NewIteratorWithStart returns a new empty iterator
|
||||||
|
func (*Database) NewIteratorWithStart([]byte) database.Iterator { return &Iterator{} }
|
||||||
|
|
||||||
|
// NewIteratorWithPrefix returns a new empty iterator
|
||||||
|
func (*Database) NewIteratorWithPrefix([]byte) database.Iterator { return &Iterator{} }
|
||||||
|
|
||||||
|
// NewIteratorWithStartAndPrefix returns a new empty iterator
|
||||||
|
func (db *Database) NewIteratorWithStartAndPrefix(start, prefix []byte) database.Iterator {
|
||||||
|
return &Iterator{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat returns an error
|
||||||
|
func (*Database) Stat(string) (string, error) { return "", database.ErrClosed }
|
||||||
|
|
||||||
|
// Compact returns nil
|
||||||
|
func (*Database) Compact(_, _ []byte) error { return database.ErrClosed }
|
||||||
|
|
||||||
|
// Close returns nil
|
||||||
|
func (*Database) Close() error { return database.ErrClosed }
|
||||||
|
|
||||||
|
// Batch does nothing
|
||||||
|
type Batch struct{}
|
||||||
|
|
||||||
|
// Put returns nil
|
||||||
|
func (*Batch) Put(_, _ []byte) error { return database.ErrClosed }
|
||||||
|
|
||||||
|
// Delete returns nil
|
||||||
|
func (*Batch) Delete([]byte) error { return database.ErrClosed }
|
||||||
|
|
||||||
|
// ValueSize returns 0
|
||||||
|
func (*Batch) ValueSize() int { return 0 }
|
||||||
|
|
||||||
|
// Write returns nil
|
||||||
|
func (*Batch) Write() error { return database.ErrClosed }
|
||||||
|
|
||||||
|
// Reset does nothing
|
||||||
|
func (*Batch) Reset() {}
|
||||||
|
|
||||||
|
// Replay does nothing
|
||||||
|
func (*Batch) Replay(database.KeyValueWriter) error { return database.ErrClosed }
|
||||||
|
|
||||||
|
// Iterator does nothing
|
||||||
|
type Iterator struct{ Err error }
|
||||||
|
|
||||||
|
// Next returns false
|
||||||
|
func (*Iterator) Next() bool { return false }
|
||||||
|
|
||||||
|
// Error returns any errors
|
||||||
|
func (it *Iterator) Error() error { return it.Err }
|
||||||
|
|
||||||
|
// Key returns nil
|
||||||
|
func (*Iterator) Key() []byte { return nil }
|
||||||
|
|
||||||
|
// Value returns nil
|
||||||
|
func (*Iterator) Value() []byte { return nil }
|
||||||
|
|
||||||
|
// Release does nothing
|
||||||
|
func (*Iterator) Release() {}
|
|
@ -0,0 +1,237 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package prefixdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/database"
|
||||||
|
"github.com/ava-labs/gecko/database/nodb"
|
||||||
|
"github.com/ava-labs/gecko/utils/hashing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Database partitions a database into a sub-database by prefixing all keys with
|
||||||
|
// a unique value.
|
||||||
|
type Database struct {
|
||||||
|
lock sync.RWMutex
|
||||||
|
dbPrefix []byte
|
||||||
|
db database.Database
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new prefixed database
|
||||||
|
func New(prefix []byte, db database.Database) *Database {
|
||||||
|
if prefixDB, ok := db.(*Database); ok {
|
||||||
|
simplePrefix := make([]byte, len(prefixDB.dbPrefix)+len(prefix))
|
||||||
|
copy(simplePrefix, prefixDB.dbPrefix)
|
||||||
|
copy(simplePrefix[len(prefixDB.dbPrefix):], prefix)
|
||||||
|
|
||||||
|
return NewNested(simplePrefix, prefixDB.db)
|
||||||
|
}
|
||||||
|
return NewNested(prefix, db)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNested returns a new prefixed database without attempting to compress
|
||||||
|
// prefixes.
|
||||||
|
func NewNested(prefix []byte, db database.Database) *Database {
|
||||||
|
return &Database{
|
||||||
|
dbPrefix: hashing.ComputeHash256(prefix),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has implements the Database interface
|
||||||
|
func (db *Database) Has(key []byte) (bool, error) {
|
||||||
|
db.lock.RLock()
|
||||||
|
defer db.lock.RUnlock()
|
||||||
|
|
||||||
|
if db.db == nil {
|
||||||
|
return false, database.ErrClosed
|
||||||
|
}
|
||||||
|
return db.db.Has(db.prefix(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implements the Database interface
|
||||||
|
func (db *Database) Get(key []byte) ([]byte, error) {
|
||||||
|
db.lock.RLock()
|
||||||
|
defer db.lock.RUnlock()
|
||||||
|
|
||||||
|
if db.db == nil {
|
||||||
|
return nil, database.ErrClosed
|
||||||
|
}
|
||||||
|
return db.db.Get(db.prefix(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put implements the Database interface
|
||||||
|
func (db *Database) Put(key, value []byte) error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
|
if db.db == nil {
|
||||||
|
return database.ErrClosed
|
||||||
|
}
|
||||||
|
return db.db.Put(db.prefix(key), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete implements the Database interface
|
||||||
|
func (db *Database) Delete(key []byte) error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
|
if db.db == nil {
|
||||||
|
return database.ErrClosed
|
||||||
|
}
|
||||||
|
return db.db.Delete(db.prefix(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBatch implements the Database interface
|
||||||
|
func (db *Database) NewBatch() database.Batch {
|
||||||
|
return &batch{
|
||||||
|
Batch: db.db.NewBatch(),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIterator implements the Database interface
|
||||||
|
func (db *Database) NewIterator() database.Iterator { return db.NewIteratorWithStartAndPrefix(nil, nil) }
|
||||||
|
|
||||||
|
// NewIteratorWithStart implements the Database interface
|
||||||
|
func (db *Database) NewIteratorWithStart(start []byte) database.Iterator {
|
||||||
|
return db.NewIteratorWithStartAndPrefix(start, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIteratorWithPrefix implements the Database interface
|
||||||
|
func (db *Database) NewIteratorWithPrefix(prefix []byte) database.Iterator {
|
||||||
|
return db.NewIteratorWithStartAndPrefix(nil, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIteratorWithStartAndPrefix implements the Database interface
|
||||||
|
func (db *Database) NewIteratorWithStartAndPrefix(start, prefix []byte) database.Iterator {
|
||||||
|
db.lock.RLock()
|
||||||
|
defer db.lock.RUnlock()
|
||||||
|
|
||||||
|
if db.db == nil {
|
||||||
|
return &nodb.Iterator{Err: database.ErrClosed}
|
||||||
|
}
|
||||||
|
return &iterator{
|
||||||
|
Iterator: db.db.NewIteratorWithStartAndPrefix(db.prefix(start), db.prefix(prefix)),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat implements the Database interface
|
||||||
|
func (db *Database) Stat(stat string) (string, error) {
|
||||||
|
db.lock.RLock()
|
||||||
|
defer db.lock.RUnlock()
|
||||||
|
|
||||||
|
if db.db == nil {
|
||||||
|
return "", database.ErrClosed
|
||||||
|
}
|
||||||
|
return db.db.Stat(stat)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compact implements the Database interface
|
||||||
|
func (db *Database) Compact(start, limit []byte) error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
|
if db.db == nil {
|
||||||
|
return database.ErrClosed
|
||||||
|
}
|
||||||
|
return db.db.Compact(db.prefix(start), db.prefix(limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements the Database interface
|
||||||
|
func (db *Database) Close() error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
|
if db.db == nil {
|
||||||
|
return database.ErrClosed
|
||||||
|
}
|
||||||
|
db.db = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *Database) prefix(key []byte) []byte {
|
||||||
|
prefixedKey := make([]byte, len(db.dbPrefix)+len(key))
|
||||||
|
copy(prefixedKey, db.dbPrefix)
|
||||||
|
copy(prefixedKey[len(db.dbPrefix):], key)
|
||||||
|
return prefixedKey
|
||||||
|
}
|
||||||
|
|
||||||
|
type keyValue struct {
|
||||||
|
key []byte
|
||||||
|
value []byte
|
||||||
|
delete bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type batch struct {
|
||||||
|
database.Batch
|
||||||
|
db *Database
|
||||||
|
writes []keyValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put implements the Batch interface
|
||||||
|
func (b *batch) Put(key, value []byte) error {
|
||||||
|
b.writes = append(b.writes, keyValue{copyBytes(key), copyBytes(value), false})
|
||||||
|
return b.Batch.Put(b.db.prefix(key), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete implements the Batch interface
|
||||||
|
func (b *batch) Delete(key []byte) error {
|
||||||
|
b.writes = append(b.writes, keyValue{copyBytes(key), nil, true})
|
||||||
|
return b.Batch.Delete(b.db.prefix(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write flushes any accumulated data to the memory database.
|
||||||
|
func (b *batch) Write() error {
|
||||||
|
b.db.lock.Lock()
|
||||||
|
defer b.db.lock.Unlock()
|
||||||
|
|
||||||
|
if b.db.db == nil {
|
||||||
|
return database.ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.Batch.Write()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset resets the batch for reuse.
|
||||||
|
func (b *batch) Reset() {
|
||||||
|
b.writes = b.writes[:0]
|
||||||
|
b.Batch.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replay replays the batch contents.
|
||||||
|
func (b *batch) Replay(w database.KeyValueWriter) error {
|
||||||
|
for _, keyvalue := range b.writes {
|
||||||
|
if keyvalue.delete {
|
||||||
|
if err := w.Delete(keyvalue.key); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if err := w.Put(keyvalue.key, keyvalue.value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type iterator struct {
|
||||||
|
database.Iterator
|
||||||
|
db *Database
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key calls the inner iterators Key and strips the prefix
|
||||||
|
func (it *iterator) Key() []byte {
|
||||||
|
key := it.Iterator.Key()
|
||||||
|
if prefixLen := len(it.db.dbPrefix); len(key) >= prefixLen {
|
||||||
|
return key[prefixLen:]
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyBytes(bytes []byte) []byte {
|
||||||
|
copiedBytes := make([]byte, len(bytes))
|
||||||
|
copy(copiedBytes, bytes)
|
||||||
|
return copiedBytes
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package prefixdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/database"
|
||||||
|
"github.com/ava-labs/gecko/database/memdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInterface(t *testing.T) {
|
||||||
|
for _, test := range database.Tests {
|
||||||
|
db := memdb.New()
|
||||||
|
test(t, New([]byte("hello"), db))
|
||||||
|
test(t, New([]byte("world"), db))
|
||||||
|
test(t, New([]byte("wor"), New([]byte("ld"), db)))
|
||||||
|
test(t, New([]byte("ld"), New([]byte("wor"), db)))
|
||||||
|
test(t, NewNested([]byte("wor"), New([]byte("ld"), db)))
|
||||||
|
test(t, NewNested([]byte("ld"), New([]byte("wor"), db)))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,551 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Tests is a list of all database tests
|
||||||
|
Tests = []func(t *testing.T, db Database){
|
||||||
|
TestSimpleKeyValue,
|
||||||
|
TestSimpleKeyValueClosed,
|
||||||
|
TestBatchPut,
|
||||||
|
TestBatchDelete,
|
||||||
|
TestBatchReset,
|
||||||
|
TestBatchReplay,
|
||||||
|
TestIterator,
|
||||||
|
TestIteratorStart,
|
||||||
|
TestIteratorPrefix,
|
||||||
|
TestIteratorStartPrefix,
|
||||||
|
TestIteratorClosed,
|
||||||
|
TestStatNoPanic,
|
||||||
|
TestCompactNoPanic,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestSimpleKeyValue ...
|
||||||
|
func TestSimpleKeyValue(t *testing.T, db Database) {
|
||||||
|
key := []byte("hello")
|
||||||
|
value := []byte("world")
|
||||||
|
|
||||||
|
if has, err := db.Has(key); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Has: %s", err)
|
||||||
|
} else if has {
|
||||||
|
t.Fatalf("db.Has unexpectedly returned true on key %s", key)
|
||||||
|
} else if v, err := db.Get(key); err != ErrNotFound {
|
||||||
|
t.Fatalf("Expected %s on db.Get for missing key %s. Returned 0x%x", ErrNotFound, key, v)
|
||||||
|
} else if err := db.Delete(key); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Delete: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Put(key, value); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Put: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if has, err := db.Has(key); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Has: %s", err)
|
||||||
|
} else if !has {
|
||||||
|
t.Fatalf("db.Has unexpectedly returned false on key %s", key)
|
||||||
|
} else if v, err := db.Get(key); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Get: %s", err)
|
||||||
|
} else if !bytes.Equal(value, v) {
|
||||||
|
t.Fatalf("db.Get: Returned: 0x%x ; Expected: 0x%x", v, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Delete(key); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Delete: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if has, err := db.Has(key); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Has: %s", err)
|
||||||
|
} else if has {
|
||||||
|
t.Fatalf("db.Has unexpectedly returned true on key %s", key)
|
||||||
|
} else if v, err := db.Get(key); err != ErrNotFound {
|
||||||
|
t.Fatalf("Expected %s on db.Get for missing key %s. Returned 0x%x", ErrNotFound, key, v)
|
||||||
|
} else if err := db.Delete(key); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Delete: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSimpleKeyValueClosed ...
|
||||||
|
func TestSimpleKeyValueClosed(t *testing.T, db Database) {
|
||||||
|
key := []byte("hello")
|
||||||
|
value := []byte("world")
|
||||||
|
|
||||||
|
if has, err := db.Has(key); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Has: %s", err)
|
||||||
|
} else if has {
|
||||||
|
t.Fatalf("db.Has unexpectedly returned true on key %s", key)
|
||||||
|
} else if v, err := db.Get(key); err != ErrNotFound {
|
||||||
|
t.Fatalf("Expected %s on db.Get for missing key %s. Returned 0x%x", ErrNotFound, key, v)
|
||||||
|
} else if err := db.Delete(key); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Delete: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Put(key, value); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Put: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if has, err := db.Has(key); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Has: %s", err)
|
||||||
|
} else if !has {
|
||||||
|
t.Fatalf("db.Has unexpectedly returned false on key %s", key)
|
||||||
|
} else if v, err := db.Get(key); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Get: %s", err)
|
||||||
|
} else if !bytes.Equal(value, v) {
|
||||||
|
t.Fatalf("db.Get: Returned: 0x%x ; Expected: 0x%x", v, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Close(); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Close: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := db.Has(key); err != ErrClosed {
|
||||||
|
t.Fatalf("Expected %s on db.Has after close", ErrClosed)
|
||||||
|
} else if _, err := db.Get(key); err != ErrClosed {
|
||||||
|
t.Fatalf("Expected %s on db.Get after close", ErrClosed)
|
||||||
|
} else if err := db.Put(key, value); err != ErrClosed {
|
||||||
|
t.Fatalf("Expected %s on db.Put after close", ErrClosed)
|
||||||
|
} else if err := db.Delete(key); err != ErrClosed {
|
||||||
|
t.Fatalf("Expected %s on db.Delete after close", ErrClosed)
|
||||||
|
} else if err := db.Close(); err != ErrClosed {
|
||||||
|
t.Fatalf("Expected %s on db.Close after close", ErrClosed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBatchPut ...
|
||||||
|
func TestBatchPut(t *testing.T, db Database) {
|
||||||
|
key := []byte("hello")
|
||||||
|
value := []byte("world")
|
||||||
|
|
||||||
|
batch := db.NewBatch()
|
||||||
|
if batch == nil {
|
||||||
|
t.Fatalf("db.NewBatch returned nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := batch.Put(key, value); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Put: %s", err)
|
||||||
|
} else if size := batch.ValueSize(); size <= 0 {
|
||||||
|
t.Fatalf("batch.ValueSize: Returned: %d ; Expected: > 0", size)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Write: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if has, err := db.Has(key); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Has: %s", err)
|
||||||
|
} else if !has {
|
||||||
|
t.Fatalf("db.Has unexpectedly returned false on key %s", key)
|
||||||
|
} else if v, err := db.Get(key); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Get: %s", err)
|
||||||
|
} else if !bytes.Equal(value, v) {
|
||||||
|
t.Fatalf("db.Get: Returned: 0x%x ; Expected: 0x%x", v, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Delete(key); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Delete: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
batch = db.NewBatch()
|
||||||
|
if batch == nil {
|
||||||
|
t.Fatalf("db.NewBatch returned nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := batch.Put(key, value); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Put: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Close()
|
||||||
|
|
||||||
|
if err := batch.Write(); err != ErrClosed {
|
||||||
|
t.Fatalf("Expected %s on batch.Write", ErrClosed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBatchDelete ...
|
||||||
|
func TestBatchDelete(t *testing.T, db Database) {
|
||||||
|
key := []byte("hello")
|
||||||
|
value := []byte("world")
|
||||||
|
|
||||||
|
if err := db.Put(key, value); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Put: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
batch := db.NewBatch()
|
||||||
|
if batch == nil {
|
||||||
|
t.Fatalf("db.NewBatch returned nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := batch.Delete(key); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Delete: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Write: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if has, err := db.Has(key); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Has: %s", err)
|
||||||
|
} else if has {
|
||||||
|
t.Fatalf("db.Has unexpectedly returned true on key %s", key)
|
||||||
|
} else if v, err := db.Get(key); err != ErrNotFound {
|
||||||
|
t.Fatalf("Expected %s on db.Get for missing key %s. Returned 0x%x", ErrNotFound, key, v)
|
||||||
|
} else if err := db.Delete(key); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Delete: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBatchReset ...
|
||||||
|
func TestBatchReset(t *testing.T, db Database) {
|
||||||
|
key := []byte("hello")
|
||||||
|
value := []byte("world")
|
||||||
|
|
||||||
|
if err := db.Put(key, value); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Put: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
batch := db.NewBatch()
|
||||||
|
if batch == nil {
|
||||||
|
t.Fatalf("db.NewBatch returned nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := batch.Delete(key); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Delete: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
batch.Reset()
|
||||||
|
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Write: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if has, err := db.Has(key); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Has: %s", err)
|
||||||
|
} else if !has {
|
||||||
|
t.Fatalf("db.Has unexpectedly returned false on key %s", key)
|
||||||
|
} else if v, err := db.Get(key); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Get: %s", err)
|
||||||
|
} else if !bytes.Equal(value, v) {
|
||||||
|
t.Fatalf("db.Get: Returned: 0x%x ; Expected: 0x%x", v, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBatchReplay ...
|
||||||
|
func TestBatchReplay(t *testing.T, db Database) {
|
||||||
|
key1 := []byte("hello1")
|
||||||
|
value1 := []byte("world1")
|
||||||
|
|
||||||
|
key2 := []byte("hello2")
|
||||||
|
value2 := []byte("world2")
|
||||||
|
|
||||||
|
batch := db.NewBatch()
|
||||||
|
if batch == nil {
|
||||||
|
t.Fatalf("db.NewBatch returned nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := batch.Put(key1, value1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Put: %s", err)
|
||||||
|
} else if err := batch.Put(key2, value2); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Put: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
secondBatch := db.NewBatch()
|
||||||
|
if secondBatch == nil {
|
||||||
|
t.Fatalf("db.NewBatch returned nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := batch.Replay(secondBatch); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Replay: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := secondBatch.Write(); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Write: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if has, err := db.Has(key1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Has: %s", err)
|
||||||
|
} else if !has {
|
||||||
|
t.Fatalf("db.Has unexpectedly returned false on key %s", key1)
|
||||||
|
} else if v, err := db.Get(key1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Get: %s", err)
|
||||||
|
} else if !bytes.Equal(value1, v) {
|
||||||
|
t.Fatalf("db.Get: Returned: 0x%x ; Expected: 0x%x", v, value1)
|
||||||
|
}
|
||||||
|
|
||||||
|
thirdBatch := db.NewBatch()
|
||||||
|
if thirdBatch == nil {
|
||||||
|
t.Fatalf("db.NewBatch returned nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := thirdBatch.Delete(key1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Delete: %s", err)
|
||||||
|
} else if err := thirdBatch.Delete(key2); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Delete: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Close(); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Close: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := batch.Replay(db); err != ErrClosed {
|
||||||
|
t.Fatalf("Expected %s on batch.Replay", ErrClosed)
|
||||||
|
} else if err := thirdBatch.Replay(db); err != ErrClosed {
|
||||||
|
t.Fatalf("Expected %s on batch.Replay", ErrClosed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIterator ...
|
||||||
|
func TestIterator(t *testing.T, db Database) {
|
||||||
|
key1 := []byte("hello1")
|
||||||
|
value1 := []byte("world1")
|
||||||
|
|
||||||
|
key2 := []byte("hello2")
|
||||||
|
value2 := []byte("world2")
|
||||||
|
|
||||||
|
if err := db.Put(key1, value1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Put: %s", err)
|
||||||
|
} else if err := db.Put(key2, value2); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Put: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator := db.NewIterator()
|
||||||
|
if iterator == nil {
|
||||||
|
t.Fatalf("db.NewIterator returned nil")
|
||||||
|
}
|
||||||
|
defer iterator.Release()
|
||||||
|
|
||||||
|
if !iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", false, true)
|
||||||
|
} else if key := iterator.Key(); !bytes.Equal(key, key1) {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: 0x%x", key, key1)
|
||||||
|
} else if value := iterator.Value(); !bytes.Equal(value, value1) {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: 0x%x", value, value1)
|
||||||
|
} else if !iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", false, true)
|
||||||
|
} else if key := iterator.Key(); !bytes.Equal(key, key2) {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: 0x%x", key, key2)
|
||||||
|
} else if value := iterator.Value(); !bytes.Equal(value, value2) {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: 0x%x", value, value2)
|
||||||
|
} else if iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", true, false)
|
||||||
|
} else if key := iterator.Key(); key != nil {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: nil", key)
|
||||||
|
} else if value := iterator.Value(); value != nil {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: nil", value)
|
||||||
|
} else if err := iterator.Error(); err != nil {
|
||||||
|
t.Fatalf("iterator.Error Returned: %s ; Expected: nil", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIteratorStart ...
|
||||||
|
func TestIteratorStart(t *testing.T, db Database) {
|
||||||
|
key1 := []byte("hello1")
|
||||||
|
value1 := []byte("world1")
|
||||||
|
|
||||||
|
key2 := []byte("hello2")
|
||||||
|
value2 := []byte("world2")
|
||||||
|
|
||||||
|
if err := db.Put(key1, value1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Put: %s", err)
|
||||||
|
} else if err := db.Put(key2, value2); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Put: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator := db.NewIteratorWithStart(key2)
|
||||||
|
if iterator == nil {
|
||||||
|
t.Fatalf("db.NewIteratorWithStart returned nil")
|
||||||
|
}
|
||||||
|
defer iterator.Release()
|
||||||
|
|
||||||
|
if !iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", false, true)
|
||||||
|
} else if key := iterator.Key(); !bytes.Equal(key, key2) {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: 0x%x", key, key2)
|
||||||
|
} else if value := iterator.Value(); !bytes.Equal(value, value2) {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: 0x%x", value, value2)
|
||||||
|
} else if iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", true, false)
|
||||||
|
} else if key := iterator.Key(); key != nil {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: nil", key)
|
||||||
|
} else if value := iterator.Value(); value != nil {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: nil", value)
|
||||||
|
} else if err := iterator.Error(); err != nil {
|
||||||
|
t.Fatalf("iterator.Error Returned: %s ; Expected: nil", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIteratorPrefix ...
|
||||||
|
func TestIteratorPrefix(t *testing.T, db Database) {
|
||||||
|
key1 := []byte("hello")
|
||||||
|
value1 := []byte("world1")
|
||||||
|
|
||||||
|
key2 := []byte("goodbye")
|
||||||
|
value2 := []byte("world2")
|
||||||
|
|
||||||
|
if err := db.Put(key1, value1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Put: %s", err)
|
||||||
|
} else if err := db.Put(key2, value2); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Put: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator := db.NewIteratorWithPrefix([]byte("h"))
|
||||||
|
if iterator == nil {
|
||||||
|
t.Fatalf("db.NewIteratorWithPrefix returned nil")
|
||||||
|
}
|
||||||
|
defer iterator.Release()
|
||||||
|
|
||||||
|
if !iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", false, true)
|
||||||
|
} else if key := iterator.Key(); !bytes.Equal(key, key1) {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: 0x%x", key, key1)
|
||||||
|
} else if value := iterator.Value(); !bytes.Equal(value, value1) {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: 0x%x", value, value1)
|
||||||
|
} else if iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", true, false)
|
||||||
|
} else if key := iterator.Key(); key != nil {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: nil", key)
|
||||||
|
} else if value := iterator.Value(); value != nil {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: nil", value)
|
||||||
|
} else if err := iterator.Error(); err != nil {
|
||||||
|
t.Fatalf("iterator.Error Returned: %s ; Expected: nil", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIteratorStartPrefix ...
|
||||||
|
func TestIteratorStartPrefix(t *testing.T, db Database) {
|
||||||
|
key1 := []byte("hello1")
|
||||||
|
value1 := []byte("world1")
|
||||||
|
|
||||||
|
key2 := []byte("z")
|
||||||
|
value2 := []byte("world2")
|
||||||
|
|
||||||
|
key3 := []byte("hello3")
|
||||||
|
value3 := []byte("world3")
|
||||||
|
|
||||||
|
if err := db.Put(key1, value1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Put: %s", err)
|
||||||
|
} else if err := db.Put(key2, value2); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Put: %s", err)
|
||||||
|
} else if err := db.Put(key3, value3); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Put: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator := db.NewIteratorWithStartAndPrefix(key1, []byte("h"))
|
||||||
|
if iterator == nil {
|
||||||
|
t.Fatalf("db.NewIteratorWithStartAndPrefix returned nil")
|
||||||
|
}
|
||||||
|
defer iterator.Release()
|
||||||
|
|
||||||
|
if !iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", false, true)
|
||||||
|
} else if key := iterator.Key(); !bytes.Equal(key, key1) {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: 0x%x", key, key1)
|
||||||
|
} else if value := iterator.Value(); !bytes.Equal(value, value1) {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: 0x%x", value, value1)
|
||||||
|
} else if !iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", false, true)
|
||||||
|
} else if key := iterator.Key(); !bytes.Equal(key, key3) {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: 0x%x", key, key3)
|
||||||
|
} else if value := iterator.Value(); !bytes.Equal(value, value3) {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: 0x%x", value, value3)
|
||||||
|
} else if iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", true, false)
|
||||||
|
} else if key := iterator.Key(); key != nil {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: nil", key)
|
||||||
|
} else if value := iterator.Value(); value != nil {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: nil", value)
|
||||||
|
} else if err := iterator.Error(); err != nil {
|
||||||
|
t.Fatalf("iterator.Error Returned: %s ; Expected: nil", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIteratorClosed ...
|
||||||
|
func TestIteratorClosed(t *testing.T, db Database) {
|
||||||
|
key1 := []byte("hello1")
|
||||||
|
value1 := []byte("world1")
|
||||||
|
|
||||||
|
if err := db.Put(key1, value1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Put: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Close(); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Close: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator := db.NewIterator()
|
||||||
|
if iterator == nil {
|
||||||
|
t.Fatalf("db.NewIterator returned nil")
|
||||||
|
}
|
||||||
|
defer iterator.Release()
|
||||||
|
|
||||||
|
if iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", true, false)
|
||||||
|
} else if key := iterator.Key(); key != nil {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: nil", key)
|
||||||
|
} else if value := iterator.Value(); value != nil {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: nil", value)
|
||||||
|
} else if err := iterator.Error(); err != ErrClosed {
|
||||||
|
t.Fatalf("Expected %s on iterator.Error", ErrClosed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestStatNoPanic ...
|
||||||
|
func TestStatNoPanic(t *testing.T, db Database) {
|
||||||
|
key1 := []byte("hello1")
|
||||||
|
value1 := []byte("world1")
|
||||||
|
|
||||||
|
key2 := []byte("z")
|
||||||
|
value2 := []byte("world2")
|
||||||
|
|
||||||
|
key3 := []byte("hello3")
|
||||||
|
value3 := []byte("world3")
|
||||||
|
|
||||||
|
if err := db.Put(key1, value1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Put: %s", err)
|
||||||
|
} else if err := db.Put(key2, value2); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Put: %s", err)
|
||||||
|
} else if err := db.Put(key3, value3); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Put: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Stat("")
|
||||||
|
|
||||||
|
if err := db.Close(); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Close: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Stat("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCompactNoPanic ...
|
||||||
|
func TestCompactNoPanic(t *testing.T, db Database) {
|
||||||
|
key1 := []byte("hello1")
|
||||||
|
value1 := []byte("world1")
|
||||||
|
|
||||||
|
key2 := []byte("z")
|
||||||
|
value2 := []byte("world2")
|
||||||
|
|
||||||
|
key3 := []byte("hello3")
|
||||||
|
value3 := []byte("world3")
|
||||||
|
|
||||||
|
if err := db.Put(key1, value1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Put: %s", err)
|
||||||
|
} else if err := db.Put(key2, value2); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Put: %s", err)
|
||||||
|
} else if err := db.Put(key3, value3); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on batch.Put: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Compact(nil, nil)
|
||||||
|
|
||||||
|
if err := db.Close(); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Close: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Compact(nil, nil)
|
||||||
|
}
|
|
@ -0,0 +1,393 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package versiondb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/database"
|
||||||
|
"github.com/ava-labs/gecko/database/memdb"
|
||||||
|
"github.com/ava-labs/gecko/database/nodb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Database implements the Database interface by living on top of another
|
||||||
|
// database, writing changes to the underlying database only when commit is
|
||||||
|
// called.
|
||||||
|
type Database struct {
|
||||||
|
lock sync.RWMutex
|
||||||
|
mem map[string]valueDelete
|
||||||
|
db database.Database
|
||||||
|
}
|
||||||
|
|
||||||
|
type valueDelete struct {
|
||||||
|
value []byte
|
||||||
|
delete bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new prefixed database
|
||||||
|
func New(db database.Database) *Database {
|
||||||
|
return &Database{
|
||||||
|
mem: make(map[string]valueDelete, memdb.DefaultSize),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has implements the database.Database interface
|
||||||
|
func (db *Database) Has(key []byte) (bool, error) {
|
||||||
|
db.lock.RLock()
|
||||||
|
defer db.lock.RUnlock()
|
||||||
|
|
||||||
|
if db.mem == nil {
|
||||||
|
return false, database.ErrClosed
|
||||||
|
}
|
||||||
|
if val, has := db.mem[string(key)]; has {
|
||||||
|
return !val.delete, nil
|
||||||
|
}
|
||||||
|
return db.db.Has(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implements the database.Database interface
|
||||||
|
func (db *Database) Get(key []byte) ([]byte, error) {
|
||||||
|
db.lock.RLock()
|
||||||
|
defer db.lock.RUnlock()
|
||||||
|
|
||||||
|
if db.mem == nil {
|
||||||
|
return nil, database.ErrClosed
|
||||||
|
}
|
||||||
|
if val, has := db.mem[string(key)]; has {
|
||||||
|
if val.delete {
|
||||||
|
return nil, database.ErrNotFound
|
||||||
|
}
|
||||||
|
return copyBytes(val.value), nil
|
||||||
|
}
|
||||||
|
return db.db.Get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put implements the database.Database interface
|
||||||
|
func (db *Database) Put(key, value []byte) error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
|
if db.mem == nil {
|
||||||
|
return database.ErrClosed
|
||||||
|
}
|
||||||
|
db.mem[string(key)] = valueDelete{value: value}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete implements the database.Database interface
|
||||||
|
func (db *Database) Delete(key []byte) error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
|
if db.mem == nil {
|
||||||
|
return database.ErrClosed
|
||||||
|
}
|
||||||
|
db.mem[string(key)] = valueDelete{delete: true}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBatch implements the database.Database interface
|
||||||
|
func (db *Database) NewBatch() database.Batch { return &batch{db: db} }
|
||||||
|
|
||||||
|
// NewIterator implements the database.Database interface
|
||||||
|
func (db *Database) NewIterator() database.Iterator { return db.NewIteratorWithStartAndPrefix(nil, nil) }
|
||||||
|
|
||||||
|
// NewIteratorWithStart implements the database.Database interface
|
||||||
|
func (db *Database) NewIteratorWithStart(start []byte) database.Iterator {
|
||||||
|
return db.NewIteratorWithStartAndPrefix(start, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIteratorWithPrefix implements the database.Database interface
|
||||||
|
func (db *Database) NewIteratorWithPrefix(prefix []byte) database.Iterator {
|
||||||
|
return db.NewIteratorWithStartAndPrefix(nil, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIteratorWithStartAndPrefix implements the database.Database interface
|
||||||
|
func (db *Database) NewIteratorWithStartAndPrefix(start, prefix []byte) database.Iterator {
|
||||||
|
db.lock.RLock()
|
||||||
|
defer db.lock.RUnlock()
|
||||||
|
|
||||||
|
if db.mem == nil {
|
||||||
|
return &nodb.Iterator{Err: database.ErrClosed}
|
||||||
|
}
|
||||||
|
|
||||||
|
startString := string(start)
|
||||||
|
prefixString := string(prefix)
|
||||||
|
keys := make([]string, 0, len(db.mem))
|
||||||
|
for key := range db.mem {
|
||||||
|
if strings.HasPrefix(key, prefixString) && key >= startString {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(keys) // Keys need to be in sorted order
|
||||||
|
values := make([]valueDelete, 0, len(keys))
|
||||||
|
for _, key := range keys {
|
||||||
|
values = append(values, db.mem[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
return &iterator{
|
||||||
|
Iterator: db.db.NewIteratorWithStartAndPrefix(start, prefix),
|
||||||
|
keys: keys,
|
||||||
|
values: values,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat implements the database.Database interface
|
||||||
|
func (db *Database) Stat(stat string) (string, error) {
|
||||||
|
db.lock.RLock()
|
||||||
|
defer db.lock.RUnlock()
|
||||||
|
|
||||||
|
if db.mem == nil {
|
||||||
|
return "", database.ErrClosed
|
||||||
|
}
|
||||||
|
return db.db.Stat(stat)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compact implements the database.Database interface
|
||||||
|
func (db *Database) Compact(start, limit []byte) error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
|
if db.mem == nil {
|
||||||
|
return database.ErrClosed
|
||||||
|
}
|
||||||
|
return db.db.Compact(start, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDatabase changes the underlying database to the specified database
|
||||||
|
func (db *Database) SetDatabase(newDB database.Database) error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
|
if db.mem == nil {
|
||||||
|
return database.ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
db.db = newDB
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDatabase returns the underlying database
|
||||||
|
func (db *Database) GetDatabase() database.Database {
|
||||||
|
db.lock.RLock()
|
||||||
|
defer db.lock.RUnlock()
|
||||||
|
|
||||||
|
return db.db
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit writes all the operations of this database to the underlying database
|
||||||
|
func (db *Database) Commit() error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
|
if db.mem == nil {
|
||||||
|
return database.ErrClosed
|
||||||
|
}
|
||||||
|
if len(db.mem) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
batch := db.db.NewBatch()
|
||||||
|
for key, value := range db.mem {
|
||||||
|
if value.delete {
|
||||||
|
if err := batch.Delete([]byte(key)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if err := batch.Put([]byte(key), value.value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
db.mem = make(map[string]valueDelete, memdb.DefaultSize)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements the database.Database interface
|
||||||
|
func (db *Database) Close() error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
|
if db.mem == nil {
|
||||||
|
return database.ErrClosed
|
||||||
|
}
|
||||||
|
db.mem = nil
|
||||||
|
db.db = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type keyValue struct {
|
||||||
|
key []byte
|
||||||
|
value []byte
|
||||||
|
delete bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type batch struct {
|
||||||
|
db *Database
|
||||||
|
writes []keyValue
|
||||||
|
size int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put implements the Database interface
|
||||||
|
func (b *batch) Put(key, value []byte) error {
|
||||||
|
b.writes = append(b.writes, keyValue{copyBytes(key), copyBytes(value), false})
|
||||||
|
b.size += len(value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete implements the Database interface
|
||||||
|
func (b *batch) Delete(key []byte) error {
|
||||||
|
b.writes = append(b.writes, keyValue{copyBytes(key), nil, true})
|
||||||
|
b.size++
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValueSize implements the Database interface
|
||||||
|
func (b *batch) ValueSize() int { return b.size }
|
||||||
|
|
||||||
|
// Write implements the Database interface
|
||||||
|
func (b *batch) Write() error {
|
||||||
|
b.db.lock.Lock()
|
||||||
|
defer b.db.lock.Unlock()
|
||||||
|
|
||||||
|
if b.db.mem == nil {
|
||||||
|
return database.ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, kv := range b.writes {
|
||||||
|
b.db.mem[string(kv.key)] = valueDelete{
|
||||||
|
value: kv.value,
|
||||||
|
delete: kv.delete,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset implements the Database interface
|
||||||
|
func (b *batch) Reset() {
|
||||||
|
b.writes = b.writes[:0]
|
||||||
|
b.size = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replay implements the Database interface
|
||||||
|
func (b *batch) Replay(w database.KeyValueWriter) error {
|
||||||
|
for _, kv := range b.writes {
|
||||||
|
if kv.delete {
|
||||||
|
if err := w.Delete(kv.key); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if err := w.Put(kv.key, kv.value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterator walks over both the in memory database and the underlying database
|
||||||
|
// at the same time.
|
||||||
|
type iterator struct {
|
||||||
|
database.Iterator
|
||||||
|
|
||||||
|
key, value []byte
|
||||||
|
|
||||||
|
keys []string
|
||||||
|
values []valueDelete
|
||||||
|
|
||||||
|
initialized, exhausted bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next moves the iterator to the next key/value pair. It returns whether the
|
||||||
|
// iterator is exhausted. We must pay careful attention to set the proper values
|
||||||
|
// based on if the in memory db or the underlying db should be read next
|
||||||
|
func (it *iterator) Next() bool {
|
||||||
|
if !it.initialized {
|
||||||
|
it.exhausted = !it.Iterator.Next()
|
||||||
|
it.initialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
switch {
|
||||||
|
case it.exhausted && len(it.keys) == 0:
|
||||||
|
it.key = nil
|
||||||
|
it.value = nil
|
||||||
|
return false
|
||||||
|
case it.exhausted:
|
||||||
|
nextKey := it.keys[0]
|
||||||
|
nextValue := it.values[0]
|
||||||
|
|
||||||
|
it.keys = it.keys[1:]
|
||||||
|
it.values = it.values[1:]
|
||||||
|
|
||||||
|
if !nextValue.delete {
|
||||||
|
it.key = []byte(nextKey)
|
||||||
|
it.value = nextValue.value
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case len(it.keys) == 0:
|
||||||
|
it.key = it.Iterator.Key()
|
||||||
|
it.value = it.Iterator.Value()
|
||||||
|
it.exhausted = !it.Iterator.Next()
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
memKey := it.keys[0]
|
||||||
|
memValue := it.values[0]
|
||||||
|
|
||||||
|
dbKey := it.Iterator.Key()
|
||||||
|
|
||||||
|
dbStringKey := string(dbKey)
|
||||||
|
switch {
|
||||||
|
case memKey < dbStringKey:
|
||||||
|
it.keys = it.keys[1:]
|
||||||
|
it.values = it.values[1:]
|
||||||
|
|
||||||
|
if !memValue.delete {
|
||||||
|
it.key = []byte(memKey)
|
||||||
|
it.value = memValue.value
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case dbStringKey < memKey:
|
||||||
|
it.key = dbKey
|
||||||
|
it.value = it.Iterator.Value()
|
||||||
|
it.exhausted = !it.Iterator.Next()
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
it.keys = it.keys[1:]
|
||||||
|
it.values = it.values[1:]
|
||||||
|
it.exhausted = !it.Iterator.Next()
|
||||||
|
|
||||||
|
if !memValue.delete {
|
||||||
|
it.key = []byte(memKey)
|
||||||
|
it.value = memValue.value
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key implements the Iterator interface
|
||||||
|
func (it *iterator) Key() []byte { return it.key }
|
||||||
|
|
||||||
|
// Value implements the Iterator interface
|
||||||
|
func (it *iterator) Value() []byte { return it.value }
|
||||||
|
|
||||||
|
// Release implements the Iterator interface
|
||||||
|
func (it *iterator) Release() {
|
||||||
|
it.key = nil
|
||||||
|
it.value = nil
|
||||||
|
it.keys = nil
|
||||||
|
it.values = nil
|
||||||
|
it.Iterator.Release()
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyBytes(bytes []byte) []byte {
|
||||||
|
copiedBytes := make([]byte, len(bytes))
|
||||||
|
copy(copiedBytes, bytes)
|
||||||
|
return copiedBytes
|
||||||
|
}
|
|
@ -0,0 +1,299 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package versiondb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/database"
|
||||||
|
"github.com/ava-labs/gecko/database/memdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInterface(t *testing.T) {
|
||||||
|
for _, test := range database.Tests {
|
||||||
|
baseDB := memdb.New()
|
||||||
|
test(t, New(baseDB))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIterate(t *testing.T) {
|
||||||
|
baseDB := memdb.New()
|
||||||
|
db := New(baseDB)
|
||||||
|
|
||||||
|
key1 := []byte("hello1")
|
||||||
|
value1 := []byte("world1")
|
||||||
|
|
||||||
|
key2 := []byte("z")
|
||||||
|
value2 := []byte("world2")
|
||||||
|
|
||||||
|
if err := db.Put(key1, value1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Put: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Commit(); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Commit: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator := db.NewIterator()
|
||||||
|
if iterator == nil {
|
||||||
|
t.Fatalf("db.NewIterator returned nil")
|
||||||
|
}
|
||||||
|
defer iterator.Release()
|
||||||
|
|
||||||
|
if !iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", false, true)
|
||||||
|
} else if key := iterator.Key(); !bytes.Equal(key, key1) {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: 0x%x", key, key1)
|
||||||
|
} else if value := iterator.Value(); !bytes.Equal(value, value1) {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: 0x%x", value, value1)
|
||||||
|
} else if iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", true, false)
|
||||||
|
} else if key := iterator.Key(); key != nil {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: nil", key)
|
||||||
|
} else if value := iterator.Value(); value != nil {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: nil", value)
|
||||||
|
} else if err := iterator.Error(); err != nil {
|
||||||
|
t.Fatalf("iterator.Error Returned: %s ; Expected: nil", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Put(key2, value2); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on database.Put: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator = db.NewIterator()
|
||||||
|
if iterator == nil {
|
||||||
|
t.Fatalf("db.NewIterator returned nil")
|
||||||
|
}
|
||||||
|
defer iterator.Release()
|
||||||
|
|
||||||
|
if !iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", false, true)
|
||||||
|
} else if key := iterator.Key(); !bytes.Equal(key, key1) {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: 0x%x", key, key1)
|
||||||
|
} else if value := iterator.Value(); !bytes.Equal(value, value1) {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: 0x%x", value, value1)
|
||||||
|
} else if !iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", false, true)
|
||||||
|
} else if key := iterator.Key(); !bytes.Equal(key, key2) {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: 0x%x", key, key2)
|
||||||
|
} else if value := iterator.Value(); !bytes.Equal(value, value2) {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: 0x%x", value, value2)
|
||||||
|
} else if iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", true, false)
|
||||||
|
} else if key := iterator.Key(); key != nil {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: nil", key)
|
||||||
|
} else if value := iterator.Value(); value != nil {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: nil", value)
|
||||||
|
} else if err := iterator.Error(); err != nil {
|
||||||
|
t.Fatalf("iterator.Error Returned: %s ; Expected: nil", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Delete(key1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on database.Delete: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator = db.NewIterator()
|
||||||
|
if iterator == nil {
|
||||||
|
t.Fatalf("db.NewIterator returned nil")
|
||||||
|
}
|
||||||
|
defer iterator.Release()
|
||||||
|
|
||||||
|
if !iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", false, true)
|
||||||
|
} else if key := iterator.Key(); !bytes.Equal(key, key2) {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: 0x%x", key, key2)
|
||||||
|
} else if value := iterator.Value(); !bytes.Equal(value, value2) {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: 0x%x", value, value2)
|
||||||
|
} else if iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", true, false)
|
||||||
|
} else if key := iterator.Key(); key != nil {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: nil", key)
|
||||||
|
} else if value := iterator.Value(); value != nil {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: nil", value)
|
||||||
|
} else if err := iterator.Error(); err != nil {
|
||||||
|
t.Fatalf("iterator.Error Returned: %s ; Expected: nil", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Commit(); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on database.Commit: %s", err)
|
||||||
|
} else if err := db.Put(key2, value1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on database.Put: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator = db.NewIterator()
|
||||||
|
if iterator == nil {
|
||||||
|
t.Fatalf("db.NewIterator returned nil")
|
||||||
|
}
|
||||||
|
defer iterator.Release()
|
||||||
|
|
||||||
|
if !iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", false, true)
|
||||||
|
} else if key := iterator.Key(); !bytes.Equal(key, key2) {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: 0x%x", key, key2)
|
||||||
|
} else if value := iterator.Value(); !bytes.Equal(value, value1) {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: 0x%x", value, value1)
|
||||||
|
} else if iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", true, false)
|
||||||
|
} else if key := iterator.Key(); key != nil {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: nil", key)
|
||||||
|
} else if value := iterator.Value(); value != nil {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: nil", value)
|
||||||
|
} else if err := iterator.Error(); err != nil {
|
||||||
|
t.Fatalf("iterator.Error Returned: %s ; Expected: nil", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Commit(); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on database.Commit: %s", err)
|
||||||
|
} else if err := db.Put(key1, value2); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on database.Put: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator = db.NewIterator()
|
||||||
|
if iterator == nil {
|
||||||
|
t.Fatalf("db.NewIterator returned nil")
|
||||||
|
}
|
||||||
|
defer iterator.Release()
|
||||||
|
|
||||||
|
if !iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", false, true)
|
||||||
|
} else if key := iterator.Key(); !bytes.Equal(key, key1) {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: 0x%x", key, key1)
|
||||||
|
} else if value := iterator.Value(); !bytes.Equal(value, value2) {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: 0x%x", value, value2)
|
||||||
|
} else if !iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", false, true)
|
||||||
|
} else if key := iterator.Key(); !bytes.Equal(key, key2) {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: 0x%x", key, key2)
|
||||||
|
} else if value := iterator.Value(); !bytes.Equal(value, value1) {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: 0x%x", value, value1)
|
||||||
|
} else if iterator.Next() {
|
||||||
|
t.Fatalf("iterator.Next Returned: %v ; Expected: %v", true, false)
|
||||||
|
} else if key := iterator.Key(); key != nil {
|
||||||
|
t.Fatalf("iterator.Key Returned: 0x%x ; Expected: nil", key)
|
||||||
|
} else if value := iterator.Value(); value != nil {
|
||||||
|
t.Fatalf("iterator.Value Returned: 0x%x ; Expected: nil", value)
|
||||||
|
} else if err := iterator.Error(); err != nil {
|
||||||
|
t.Fatalf("iterator.Error Returned: %s ; Expected: nil", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommit(t *testing.T) {
|
||||||
|
baseDB := memdb.New()
|
||||||
|
db := New(baseDB)
|
||||||
|
|
||||||
|
if err := db.Commit(); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Commit: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
key1 := []byte("hello1")
|
||||||
|
value1 := []byte("world1")
|
||||||
|
|
||||||
|
if err := db.Put(key1, value1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Put: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Commit(); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Commit: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if value, err := db.Get(key1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Get: %s", err)
|
||||||
|
} else if !bytes.Equal(value, value1) {
|
||||||
|
t.Fatalf("db.Get Returned: 0x%x ; Expected: 0x%x", value, value1)
|
||||||
|
} else if value, err := baseDB.Get(key1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Get: %s", err)
|
||||||
|
} else if !bytes.Equal(value, value1) {
|
||||||
|
t.Fatalf("db.Get Returned: 0x%x ; Expected: 0x%x", value, value1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommitClosed(t *testing.T) {
|
||||||
|
baseDB := memdb.New()
|
||||||
|
db := New(baseDB)
|
||||||
|
|
||||||
|
key1 := []byte("hello1")
|
||||||
|
value1 := []byte("world1")
|
||||||
|
|
||||||
|
if err := db.Put(key1, value1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Put: %s", err)
|
||||||
|
} else if err := db.Close(); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Close: %s", err)
|
||||||
|
} else if err := db.Commit(); err != database.ErrClosed {
|
||||||
|
t.Fatalf("Expected %s on db.Commit", database.ErrClosed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommitClosedWrite(t *testing.T) {
|
||||||
|
baseDB := memdb.New()
|
||||||
|
db := New(baseDB)
|
||||||
|
|
||||||
|
key1 := []byte("hello1")
|
||||||
|
value1 := []byte("world1")
|
||||||
|
|
||||||
|
baseDB.Close()
|
||||||
|
|
||||||
|
if err := db.Put(key1, value1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Put: %s", err)
|
||||||
|
} else if err := db.Commit(); err != database.ErrClosed {
|
||||||
|
t.Fatalf("Expected %s on db.Commit", database.ErrClosed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommitClosedDelete(t *testing.T) {
|
||||||
|
baseDB := memdb.New()
|
||||||
|
db := New(baseDB)
|
||||||
|
|
||||||
|
key1 := []byte("hello1")
|
||||||
|
|
||||||
|
baseDB.Close()
|
||||||
|
|
||||||
|
if err := db.Delete(key1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Delete: %s", err)
|
||||||
|
} else if err := db.Commit(); err != database.ErrClosed {
|
||||||
|
t.Fatalf("Expected %s on db.Commit", database.ErrClosed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetDatabase(t *testing.T) {
|
||||||
|
baseDB := memdb.New()
|
||||||
|
newDB := memdb.New()
|
||||||
|
db := New(baseDB)
|
||||||
|
|
||||||
|
key1 := []byte("hello1")
|
||||||
|
value1 := []byte("world1")
|
||||||
|
|
||||||
|
if err := db.SetDatabase(newDB); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.SetDatabase: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if db.GetDatabase() != newDB {
|
||||||
|
t.Fatalf("Unexpected database from db.GetDatabase")
|
||||||
|
} else if err := db.Put(key1, value1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Put: %s", err)
|
||||||
|
} else if err := db.Commit(); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Commit: %s", err)
|
||||||
|
} else if has, err := baseDB.Has(key1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Has: %s", err)
|
||||||
|
} else if has {
|
||||||
|
t.Fatalf("db.Has Returned: %v ; Expected: %v", has, false)
|
||||||
|
} else if has, err := newDB.Has(key1); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Has: %s", err)
|
||||||
|
} else if !has {
|
||||||
|
t.Fatalf("db.Has Returned: %v ; Expected: %v", has, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetDatabaseClosed(t *testing.T) {
|
||||||
|
baseDB := memdb.New()
|
||||||
|
db := New(baseDB)
|
||||||
|
|
||||||
|
if err := db.Close(); err != nil {
|
||||||
|
t.Fatalf("Unexpected error on db.Close: %s", err)
|
||||||
|
} else if err := db.SetDatabase(memdb.New()); err != database.ErrClosed {
|
||||||
|
t.Fatalf("Expected %s on db.SetDatabase", database.ErrClosed)
|
||||||
|
} else if db.GetDatabase() != nil {
|
||||||
|
t.Fatalf("Unexpected database from db.GetDatabase")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,513 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package genesis
|
||||||
|
|
||||||
|
// TODO: Move this to a separate repo and leave only a byte array
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/ids"
|
||||||
|
"github.com/ava-labs/gecko/vms/avm"
|
||||||
|
"github.com/ava-labs/gecko/vms/evm"
|
||||||
|
"github.com/ava-labs/gecko/vms/platformvm"
|
||||||
|
"github.com/ava-labs/gecko/vms/spchainvm"
|
||||||
|
"github.com/ava-labs/gecko/vms/spdagvm"
|
||||||
|
"github.com/ava-labs/gecko/vms/timestampvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Note that since an AVA network has exactly one Platform Chain,
|
||||||
|
// and the Platform Chain defines the genesis state of the network
|
||||||
|
// (who is staking, which chains exist, etc.), defining the genesis
|
||||||
|
// state of the Platform Chain is the same as defining the genesis
|
||||||
|
// state of the network.
|
||||||
|
|
||||||
|
// Hardcoded network IDs
|
||||||
|
const (
|
||||||
|
MainnetID uint32 = 1
|
||||||
|
TestnetID uint32 = 2
|
||||||
|
BorealisID uint32 = 2
|
||||||
|
LocalID uint32 = 12345
|
||||||
|
|
||||||
|
MainnetName = "mainnet"
|
||||||
|
TestnetName = "testnet"
|
||||||
|
BorealisName = "borealis"
|
||||||
|
LocalName = "local"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
validNetworkName = regexp.MustCompile(`network-[0-9]+`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Hard coded genesis constants
|
||||||
|
var (
|
||||||
|
// Give special names to the mainnet and testnet
|
||||||
|
NetworkIDToNetworkName = map[uint32]string{
|
||||||
|
MainnetID: MainnetName,
|
||||||
|
TestnetID: BorealisName,
|
||||||
|
LocalID: LocalName,
|
||||||
|
}
|
||||||
|
NetworkNameToNetworkID = map[string]uint32{
|
||||||
|
MainnetName: MainnetID,
|
||||||
|
TestnetName: TestnetID,
|
||||||
|
BorealisName: BorealisID,
|
||||||
|
LocalName: LocalID,
|
||||||
|
}
|
||||||
|
Keys = []string{
|
||||||
|
"ewoqjP7PxY4yr3iLTpLisriqt94hdyDFNgchSxGGztUrTXtNN",
|
||||||
|
}
|
||||||
|
Addresses = []string{
|
||||||
|
"6Y3kysjF9jnHnYkdS9yGAuoHyae2eNmeV",
|
||||||
|
}
|
||||||
|
ParsedAddresses = []ids.ShortID{}
|
||||||
|
StakerIDs = []string{
|
||||||
|
"7Xhw2mDxuDS44j42TCB6U5579esbSt3Lg",
|
||||||
|
"MFrZFVCXPv5iCn6M9K6XduxGTYp891xXZ",
|
||||||
|
"NFBbbJ4qCmNaCzeW7sxErhvWqvEQMnYcN",
|
||||||
|
"GWPcbFJZFfZreETSoWjPimr846mXEKCtu",
|
||||||
|
"P7oB2McjBGgW2NXXWVYjV8JEDFoW9xDE5",
|
||||||
|
}
|
||||||
|
ParsedStakerIDs = []ids.ShortID{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for _, addrStr := range Addresses {
|
||||||
|
addr, err := ids.ShortFromString(addrStr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
ParsedAddresses = append(ParsedAddresses, addr)
|
||||||
|
}
|
||||||
|
for _, stakerIDStr := range StakerIDs {
|
||||||
|
stakerID, err := ids.ShortFromString(stakerIDStr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
ParsedStakerIDs = append(ParsedStakerIDs, stakerID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkName returns a human readable name for the network with
|
||||||
|
// ID [networkID]
|
||||||
|
func NetworkName(networkID uint32) string {
|
||||||
|
if name, exists := NetworkIDToNetworkName[networkID]; exists {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("network-%d", networkID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkID returns the ID of the network with name [networkName]
|
||||||
|
func NetworkID(networkName string) (uint32, error) {
|
||||||
|
networkName = strings.ToLower(networkName)
|
||||||
|
if id, exists := NetworkNameToNetworkID[networkName]; exists {
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if id, err := strconv.ParseUint(networkName, 10, 0); err == nil {
|
||||||
|
if id > math.MaxUint32 {
|
||||||
|
return 0, fmt.Errorf("NetworkID %s not in [0, 2^32)", networkName)
|
||||||
|
}
|
||||||
|
return uint32(id), nil
|
||||||
|
}
|
||||||
|
if validNetworkName.MatchString(networkName) {
|
||||||
|
if id, err := strconv.Atoi(networkName[8:]); err == nil {
|
||||||
|
if id > math.MaxUint32 {
|
||||||
|
return 0, fmt.Errorf("NetworkID %s not in [0, 2^32)", networkName)
|
||||||
|
}
|
||||||
|
return uint32(id), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, fmt.Errorf("Failed to parse %s as a network name", networkName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aliases returns the default aliases based on the network ID
|
||||||
|
func Aliases(networkID uint32) (generalAliases map[string][]string, chainAliases map[[32]byte][]string, vmAliases map[[32]byte][]string) {
|
||||||
|
generalAliases = map[string][]string{
|
||||||
|
"vm/" + platformvm.ID.String(): []string{"vm/platform"},
|
||||||
|
"vm/" + avm.ID.String(): []string{"vm/avm"},
|
||||||
|
"vm/" + evm.ID.String(): []string{"vm/evm"},
|
||||||
|
"vm/" + spdagvm.ID.String(): []string{"vm/spdag"},
|
||||||
|
"vm/" + spchainvm.ID.String(): []string{"vm/spchain"},
|
||||||
|
"vm/" + timestampvm.ID.String(): []string{"vm/timestamp"},
|
||||||
|
"bc/" + ids.Empty.String(): []string{"P", "platform", "bc/P", "bc/platform"},
|
||||||
|
}
|
||||||
|
chainAliases = map[[32]byte][]string{
|
||||||
|
ids.Empty.Key(): []string{"P", "platform"},
|
||||||
|
}
|
||||||
|
vmAliases = map[[32]byte][]string{
|
||||||
|
platformvm.ID.Key(): []string{"platform"},
|
||||||
|
avm.ID.Key(): []string{"avm"},
|
||||||
|
evm.ID.Key(): []string{"evm"},
|
||||||
|
spdagvm.ID.Key(): []string{"spdag"},
|
||||||
|
spchainvm.ID.Key(): []string{"spchain"},
|
||||||
|
timestampvm.ID.Key(): []string{"timestamp"},
|
||||||
|
}
|
||||||
|
|
||||||
|
genesisBytes := Genesis(networkID)
|
||||||
|
genesis := &platformvm.Genesis{} // TODO let's not re-create genesis to do aliasing
|
||||||
|
platformvm.Codec.Unmarshal(genesisBytes, genesis) // TODO check for error
|
||||||
|
genesis.Initialize()
|
||||||
|
|
||||||
|
for _, chain := range genesis.Chains {
|
||||||
|
switch {
|
||||||
|
case avm.ID.Equals(chain.VMID):
|
||||||
|
generalAliases["bc/"+chain.ID().String()] = []string{"X", "avm", "bc/X", "bc/avm"}
|
||||||
|
chainAliases[chain.ID().Key()] = []string{"X", "avm"}
|
||||||
|
case evm.ID.Equals(chain.VMID):
|
||||||
|
generalAliases["bc/"+chain.ID().String()] = []string{"C", "evm", "bc/C", "bc/evm"}
|
||||||
|
chainAliases[chain.ID().Key()] = []string{"C", "evm"}
|
||||||
|
case spdagvm.ID.Equals(chain.VMID):
|
||||||
|
generalAliases["bc/"+chain.ID().String()] = []string{"bc/spdag"}
|
||||||
|
chainAliases[chain.ID().Key()] = []string{"spdag"}
|
||||||
|
case spchainvm.ID.Equals(chain.VMID):
|
||||||
|
generalAliases["bc/"+chain.ID().String()] = []string{"bc/spchain"}
|
||||||
|
chainAliases[chain.ID().Key()] = []string{"spchain"}
|
||||||
|
case timestampvm.ID.Equals(chain.VMID):
|
||||||
|
generalAliases["bc/"+chain.ID().String()] = []string{"bc/timestamp"}
|
||||||
|
chainAliases[chain.ID().Key()] = []string{"timestamp"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Genesis returns the genesis data of the Platform Chain.
|
||||||
|
// Since the Platform Chain causes the creation of all other
|
||||||
|
// chains, this function returns the genesis data of the entire network.
|
||||||
|
// The ID of the new network is [networkID].
|
||||||
|
func Genesis(networkID uint32) []byte {
|
||||||
|
if networkID != LocalID {
|
||||||
|
panic("unknown network ID provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte{
|
||||||
|
0x00, 0x00, 0x00, 0x01, 0x3c, 0xb7, 0xd3, 0x84,
|
||||||
|
0x2e, 0x8c, 0xee, 0x6a, 0x0e, 0xbd, 0x09, 0xf1,
|
||||||
|
0xfe, 0x88, 0x4f, 0x68, 0x61, 0xe1, 0xb2, 0x9c,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x12, 0x30, 0x9c, 0xe5, 0x40, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
|
||||||
|
0x05, 0xde, 0x31, 0xb4, 0xd8, 0xb2, 0x29, 0x91,
|
||||||
|
0xd5, 0x1a, 0xa6, 0xaa, 0x1f, 0xc7, 0x33, 0xf2,
|
||||||
|
0x3a, 0x85, 0x1a, 0x8c, 0x94, 0x00, 0x00, 0x12,
|
||||||
|
0x30, 0x9c, 0xe5, 0x40, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x5d, 0xbb, 0x75, 0x80, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x5f, 0x9c, 0xa9, 0x00, 0x00, 0x00, 0x30,
|
||||||
|
0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x3c, 0xb7, 0xd3, 0x84, 0x2e, 0x8c, 0xee,
|
||||||
|
0x6a, 0x0e, 0xbd, 0x09, 0xf1, 0xfe, 0x88, 0x4f,
|
||||||
|
0x68, 0x61, 0xe1, 0xb2, 0x9c, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xaa, 0x18,
|
||||||
|
0xd3, 0x99, 0x1c, 0xf6, 0x37, 0xaa, 0x6c, 0x16,
|
||||||
|
0x2f, 0x5e, 0x95, 0xcf, 0x16, 0x3f, 0x69, 0xcd,
|
||||||
|
0x82, 0x91, 0x00, 0x00, 0x12, 0x30, 0x9c, 0xe5,
|
||||||
|
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, 0xbb,
|
||||||
|
0x75, 0x80, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x9c,
|
||||||
|
0xa9, 0x00, 0x00, 0x00, 0x30, 0x39, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0xb7,
|
||||||
|
0xd3, 0x84, 0x2e, 0x8c, 0xee, 0x6a, 0x0e, 0xbd,
|
||||||
|
0x09, 0xf1, 0xfe, 0x88, 0x4f, 0x68, 0x61, 0xe1,
|
||||||
|
0xb2, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x05, 0xe9, 0x09, 0x4f, 0x73, 0x69,
|
||||||
|
0x80, 0x02, 0xfd, 0x52, 0xc9, 0x08, 0x19, 0xb4,
|
||||||
|
0x57, 0xb9, 0xfb, 0xc8, 0x66, 0xab, 0x80, 0x00,
|
||||||
|
0x00, 0x12, 0x30, 0x9c, 0xe5, 0x40, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x5d, 0xbb, 0x75, 0x80, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x5f, 0x9c, 0xa9, 0x00, 0x00,
|
||||||
|
0x00, 0x30, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x3c, 0xb7, 0xd3, 0x84, 0x2e,
|
||||||
|
0x8c, 0xee, 0x6a, 0x0e, 0xbd, 0x09, 0xf1, 0xfe,
|
||||||
|
0x88, 0x4f, 0x68, 0x61, 0xe1, 0xb2, 0x9c, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
|
||||||
|
0x47, 0x9f, 0x66, 0xc8, 0xbe, 0x89, 0x58, 0x30,
|
||||||
|
0x54, 0x7e, 0x70, 0xb4, 0xb2, 0x98, 0xca, 0xfd,
|
||||||
|
0x43, 0x3d, 0xba, 0x6e, 0x00, 0x00, 0x12, 0x30,
|
||||||
|
0x9c, 0xe5, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x5d, 0xbb, 0x75, 0x80, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x5f, 0x9c, 0xa9, 0x00, 0x00, 0x00, 0x30, 0x39,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x3c, 0xb7, 0xd3, 0x84, 0x2e, 0x8c, 0xee, 0x6a,
|
||||||
|
0x0e, 0xbd, 0x09, 0xf1, 0xfe, 0x88, 0x4f, 0x68,
|
||||||
|
0x61, 0xe1, 0xb2, 0x9c, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x05, 0xf2, 0x9b, 0xce,
|
||||||
|
0x5f, 0x34, 0xa7, 0x43, 0x01, 0xeb, 0x0d, 0xe7,
|
||||||
|
0x16, 0xd5, 0x19, 0x4e, 0x4a, 0x4a, 0xea, 0x5d,
|
||||||
|
0x7a, 0x00, 0x00, 0x12, 0x30, 0x9c, 0xe5, 0x40,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, 0xbb, 0x75,
|
||||||
|
0x80, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x9c, 0xa9,
|
||||||
|
0x00, 0x00, 0x00, 0x30, 0x39, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0xb7, 0xd3,
|
||||||
|
0x84, 0x2e, 0x8c, 0xee, 0x6a, 0x0e, 0xbd, 0x09,
|
||||||
|
0xf1, 0xfe, 0x88, 0x4f, 0x68, 0x61, 0xe1, 0xb2,
|
||||||
|
0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x05, 0x00, 0x00, 0x30, 0x39, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
|
||||||
|
0x41, 0x56, 0x4d, 0x61, 0x76, 0x6d, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x73,
|
||||||
|
0x65, 0x63, 0x70, 0x32, 0x35, 0x36, 0x6b, 0x31,
|
||||||
|
0x66, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
0x03, 0x41, 0x56, 0x41, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x03, 0x41, 0x56, 0x41, 0x00, 0x03, 0x41,
|
||||||
|
0x56, 0x41, 0x09, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
0x00, 0x00, 0x04, 0x00, 0x9f, 0xdf, 0x42, 0xf6,
|
||||||
|
0xe4, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
0x00, 0x00, 0x01, 0x3c, 0xb7, 0xd3, 0x84, 0x2e,
|
||||||
|
0x8c, 0xee, 0x6a, 0x0e, 0xbd, 0x09, 0xf1, 0xfe,
|
||||||
|
0x88, 0x4f, 0x68, 0x61, 0xe1, 0xb2, 0x9c, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x30, 0x39, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x41, 0x74,
|
||||||
|
0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x65, 0x76,
|
||||||
|
0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x02, 0xc9, 0x7b, 0x22,
|
||||||
|
0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x3a,
|
||||||
|
0x7b, 0x22, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49,
|
||||||
|
0x64, 0x22, 0x3a, 0x34, 0x33, 0x31, 0x31, 0x30,
|
||||||
|
0x2c, 0x22, 0x68, 0x6f, 0x6d, 0x65, 0x73, 0x74,
|
||||||
|
0x65, 0x61, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b,
|
||||||
|
0x22, 0x3a, 0x30, 0x2c, 0x22, 0x64, 0x61, 0x6f,
|
||||||
|
0x46, 0x6f, 0x72, 0x6b, 0x42, 0x6c, 0x6f, 0x63,
|
||||||
|
0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x64, 0x61,
|
||||||
|
0x6f, 0x46, 0x6f, 0x72, 0x6b, 0x53, 0x75, 0x70,
|
||||||
|
0x70, 0x6f, 0x72, 0x74, 0x22, 0x3a, 0x74, 0x72,
|
||||||
|
0x75, 0x65, 0x2c, 0x22, 0x65, 0x69, 0x70, 0x31,
|
||||||
|
0x35, 0x30, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x22,
|
||||||
|
0x3a, 0x30, 0x2c, 0x22, 0x65, 0x69, 0x70, 0x31,
|
||||||
|
0x35, 0x30, 0x48, 0x61, 0x73, 0x68, 0x22, 0x3a,
|
||||||
|
0x22, 0x30, 0x78, 0x32, 0x30, 0x38, 0x36, 0x37,
|
||||||
|
0x39, 0x39, 0x61, 0x65, 0x65, 0x62, 0x65, 0x61,
|
||||||
|
0x65, 0x31, 0x33, 0x35, 0x63, 0x32, 0x34, 0x36,
|
||||||
|
0x63, 0x36, 0x35, 0x30, 0x32, 0x31, 0x63, 0x38,
|
||||||
|
0x32, 0x62, 0x34, 0x65, 0x31, 0x35, 0x61, 0x32,
|
||||||
|
0x63, 0x34, 0x35, 0x31, 0x33, 0x34, 0x30, 0x39,
|
||||||
|
0x39, 0x33, 0x61, 0x61, 0x63, 0x66, 0x64, 0x32,
|
||||||
|
0x37, 0x35, 0x31, 0x38, 0x38, 0x36, 0x35, 0x31,
|
||||||
|
0x34, 0x66, 0x30, 0x22, 0x2c, 0x22, 0x65, 0x69,
|
||||||
|
0x70, 0x31, 0x35, 0x35, 0x42, 0x6c, 0x6f, 0x63,
|
||||||
|
0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x65, 0x69,
|
||||||
|
0x70, 0x31, 0x35, 0x38, 0x42, 0x6c, 0x6f, 0x63,
|
||||||
|
0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x62, 0x79,
|
||||||
|
0x7a, 0x61, 0x6e, 0x74, 0x69, 0x75, 0x6d, 0x42,
|
||||||
|
0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x3a, 0x30, 0x2c,
|
||||||
|
0x22, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x61, 0x6e,
|
||||||
|
0x74, 0x69, 0x6e, 0x6f, 0x70, 0x6c, 0x65, 0x42,
|
||||||
|
0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x3a, 0x30, 0x2c,
|
||||||
|
0x22, 0x70, 0x65, 0x74, 0x65, 0x72, 0x73, 0x62,
|
||||||
|
0x75, 0x72, 0x67, 0x42, 0x6c, 0x6f, 0x63, 0x6b,
|
||||||
|
0x22, 0x3a, 0x30, 0x7d, 0x2c, 0x22, 0x6e, 0x6f,
|
||||||
|
0x6e, 0x63, 0x65, 0x22, 0x3a, 0x22, 0x30, 0x78,
|
||||||
|
0x30, 0x22, 0x2c, 0x22, 0x74, 0x69, 0x6d, 0x65,
|
||||||
|
0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x3a, 0x22,
|
||||||
|
0x30, 0x78, 0x30, 0x22, 0x2c, 0x22, 0x65, 0x78,
|
||||||
|
0x74, 0x72, 0x61, 0x44, 0x61, 0x74, 0x61, 0x22,
|
||||||
|
0x3a, 0x22, 0x30, 0x78, 0x30, 0x30, 0x22, 0x2c,
|
||||||
|
0x22, 0x67, 0x61, 0x73, 0x4c, 0x69, 0x6d, 0x69,
|
||||||
|
0x74, 0x22, 0x3a, 0x22, 0x30, 0x78, 0x35, 0x66,
|
||||||
|
0x35, 0x65, 0x31, 0x30, 0x30, 0x22, 0x2c, 0x22,
|
||||||
|
0x64, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c,
|
||||||
|
0x74, 0x79, 0x22, 0x3a, 0x22, 0x30, 0x78, 0x30,
|
||||||
|
0x22, 0x2c, 0x22, 0x6d, 0x69, 0x78, 0x48, 0x61,
|
||||||
|
0x73, 0x68, 0x22, 0x3a, 0x22, 0x30, 0x78, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x22,
|
||||||
|
0x2c, 0x22, 0x63, 0x6f, 0x69, 0x6e, 0x62, 0x61,
|
||||||
|
0x73, 0x65, 0x22, 0x3a, 0x22, 0x30, 0x78, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x22,
|
||||||
|
0x2c, 0x22, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x22,
|
||||||
|
0x3a, 0x7b, 0x22, 0x37, 0x35, 0x31, 0x61, 0x30,
|
||||||
|
0x62, 0x39, 0x36, 0x65, 0x31, 0x30, 0x34, 0x32,
|
||||||
|
0x62, 0x65, 0x65, 0x37, 0x38, 0x39, 0x34, 0x35,
|
||||||
|
0x32, 0x65, 0x63, 0x62, 0x32, 0x30, 0x32, 0x35,
|
||||||
|
0x33, 0x66, 0x62, 0x61, 0x34, 0x30, 0x64, 0x62,
|
||||||
|
0x65, 0x38, 0x35, 0x22, 0x3a, 0x7b, 0x22, 0x62,
|
||||||
|
0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x3a,
|
||||||
|
0x22, 0x30, 0x78, 0x33, 0x33, 0x62, 0x32, 0x65,
|
||||||
|
0x33, 0x63, 0x39, 0x66, 0x64, 0x30, 0x38, 0x30,
|
||||||
|
0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x22, 0x7d, 0x7d, 0x2c, 0x22, 0x6e,
|
||||||
|
0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x3a, 0x22,
|
||||||
|
0x30, 0x78, 0x30, 0x22, 0x2c, 0x22, 0x67, 0x61,
|
||||||
|
0x73, 0x55, 0x73, 0x65, 0x64, 0x22, 0x3a, 0x22,
|
||||||
|
0x30, 0x78, 0x30, 0x22, 0x2c, 0x22, 0x70, 0x61,
|
||||||
|
0x72, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68,
|
||||||
|
0x22, 0x3a, 0x22, 0x30, 0x78, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x22, 0x7d, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x30, 0x39, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x53, 0x69,
|
||||||
|
0x6d, 0x70, 0x6c, 0x65, 0x20, 0x44, 0x41, 0x47,
|
||||||
|
0x20, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74,
|
||||||
|
0x73, 0x73, 0x70, 0x64, 0x61, 0x67, 0x76, 0x6d,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x60, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x12, 0x30, 0x9c, 0xe5, 0x40,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
0x01, 0x3c, 0xb7, 0xd3, 0x84, 0x2e, 0x8c, 0xee,
|
||||||
|
0x6a, 0x0e, 0xbd, 0x09, 0xf1, 0xfe, 0x88, 0x4f,
|
||||||
|
0x68, 0x61, 0xe1, 0xb2, 0x9c, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x30, 0x39, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15,
|
||||||
|
0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x43,
|
||||||
|
0x68, 0x61, 0x69, 0x6e, 0x20, 0x50, 0x61, 0x79,
|
||||||
|
0x6d, 0x65, 0x6e, 0x74, 0x73, 0x73, 0x70, 0x63,
|
||||||
|
0x68, 0x61, 0x69, 0x6e, 0x76, 0x6d, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||||
|
0x01, 0x3c, 0xb7, 0xd3, 0x84, 0x2e, 0x8c, 0xee,
|
||||||
|
0x6a, 0x0e, 0xbd, 0x09, 0xf1, 0xfe, 0x88, 0x4f,
|
||||||
|
0x68, 0x61, 0xe1, 0xb2, 0x9c, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12,
|
||||||
|
0x30, 0x9c, 0xe5, 0x40, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x30, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x17, 0x53, 0x69, 0x6d, 0x70,
|
||||||
|
0x6c, 0x65, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x73,
|
||||||
|
0x74, 0x61, 0x6d, 0x70, 0x20, 0x53, 0x65, 0x72,
|
||||||
|
0x76, 0x65, 0x72, 0x74, 0x69, 0x6d, 0x65, 0x73,
|
||||||
|
0x74, 0x61, 0x6d, 0x70, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x5d, 0xbb, 0x75, 0x80,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VMGenesis ...
|
||||||
|
func VMGenesis(networkID uint32, vmID ids.ID) *platformvm.CreateChainTx {
|
||||||
|
genesisBytes := Genesis(networkID)
|
||||||
|
genesis := platformvm.Genesis{}
|
||||||
|
platformvm.Codec.Unmarshal(genesisBytes, &genesis)
|
||||||
|
for _, chain := range genesis.Chains {
|
||||||
|
if chain.VMID.Equals(vmID) {
|
||||||
|
return chain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package genesis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/vms/avm"
|
||||||
|
"github.com/ava-labs/gecko/vms/evm"
|
||||||
|
"github.com/ava-labs/gecko/vms/platformvm"
|
||||||
|
"github.com/ava-labs/gecko/vms/spchainvm"
|
||||||
|
"github.com/ava-labs/gecko/vms/spdagvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNetworkName(t *testing.T) {
|
||||||
|
if name := NetworkName(MainnetID); name != MainnetName {
|
||||||
|
t.Fatalf("NetworkID was incorrectly named. Result: %s ; Expected: %s", name, MainnetName)
|
||||||
|
}
|
||||||
|
if name := NetworkName(TestnetID); name != BorealisName {
|
||||||
|
t.Fatalf("NetworkID was incorrectly named. Result: %s ; Expected: %s", name, BorealisName)
|
||||||
|
}
|
||||||
|
if name := NetworkName(BorealisID); name != BorealisName {
|
||||||
|
t.Fatalf("NetworkID was incorrectly named. Result: %s ; Expected: %s", name, BorealisName)
|
||||||
|
}
|
||||||
|
if name := NetworkName(4294967295); name != "network-4294967295" {
|
||||||
|
t.Fatalf("NetworkID was incorrectly named. Result: %s ; Expected: %s", name, "network-4294967295")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNetworkID(t *testing.T) {
|
||||||
|
id, err := NetworkID(MainnetName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if id != MainnetID {
|
||||||
|
t.Fatalf("Returned wrong network. Expected: %d ; Returned %d", MainnetID, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err = NetworkID(TestnetName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if id != TestnetID {
|
||||||
|
t.Fatalf("Returned wrong network. Expected: %d ; Returned %d", TestnetID, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err = NetworkID(BorealisName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if id != TestnetID {
|
||||||
|
t.Fatalf("Returned wrong network. Expected: %d ; Returned %d", TestnetID, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err = NetworkID("bOrEaLiS")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if id != TestnetID {
|
||||||
|
t.Fatalf("Returned wrong network. Expected: %d ; Returned %d", TestnetID, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err = NetworkID("network-4294967295")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if id != 4294967295 {
|
||||||
|
t.Fatalf("Returned wrong network. Expected: %d ; Returned %d", 4294967295, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err = NetworkID("4294967295")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if id != 4294967295 {
|
||||||
|
t.Fatalf("Returned wrong network. Expected: %d ; Returned %d", 4294967295, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := NetworkID("network-4294967296"); err == nil {
|
||||||
|
t.Fatalf("Should have errored due to the network being too large.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := NetworkID("4294967296"); err == nil {
|
||||||
|
t.Fatalf("Should have errored due to the network being too large.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := NetworkID("asdcvasdc-252"); err == nil {
|
||||||
|
t.Fatalf("Should have errored due to the invalid input string.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAliases(t *testing.T) {
|
||||||
|
generalAliases, _, _ := Aliases(LocalID)
|
||||||
|
if _, exists := generalAliases["vm/"+platformvm.ID.String()]; !exists {
|
||||||
|
t.Fatalf("Should have a custom alias from the vm")
|
||||||
|
} else if _, exists := generalAliases["vm/"+avm.ID.String()]; !exists {
|
||||||
|
t.Fatalf("Should have a custom alias from the vm")
|
||||||
|
} else if _, exists := generalAliases["vm/"+evm.ID.String()]; !exists {
|
||||||
|
t.Fatalf("Should have a custom alias from the vm")
|
||||||
|
} else if _, exists := generalAliases["vm/"+spdagvm.ID.String()]; !exists {
|
||||||
|
t.Fatalf("Should have a custom alias from the vm")
|
||||||
|
} else if _, exists := generalAliases["vm/"+spchainvm.ID.String()]; !exists {
|
||||||
|
t.Fatalf("Should have a custom alias from the vm")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenesis(t *testing.T) {
|
||||||
|
genesisBytes := Genesis(LocalID)
|
||||||
|
genesis := platformvm.Genesis{}
|
||||||
|
if err := platformvm.Codec.Unmarshal(genesisBytes, &genesis); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package ids
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Aliaser allows one to give an ID aliases and lookup the aliases given to an
|
||||||
|
// ID. An ID can have arbitrarily many aliases; two IDs may not have the same
|
||||||
|
// alias.
|
||||||
|
type Aliaser struct {
|
||||||
|
dealias map[string]ID
|
||||||
|
aliases map[[32]byte][]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the aliaser to have no aliases
|
||||||
|
func (a *Aliaser) Initialize() {
|
||||||
|
a.dealias = make(map[string]ID)
|
||||||
|
a.aliases = make(map[[32]byte][]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup returns the ID associated with alias
|
||||||
|
func (a *Aliaser) Lookup(alias string) (ID, error) {
|
||||||
|
if ID, ok := a.dealias[alias]; ok {
|
||||||
|
return ID, nil
|
||||||
|
}
|
||||||
|
return ID{}, fmt.Errorf("there is no ID with alias %s", alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aliases returns the aliases of an ID
|
||||||
|
func (a Aliaser) Aliases(id ID) []string { return a.aliases[id.Key()] }
|
||||||
|
|
||||||
|
// PrimaryAlias returns the first alias of [id]
|
||||||
|
func (a Aliaser) PrimaryAlias(id ID) (string, error) {
|
||||||
|
aliases, exists := a.aliases[id.Key()]
|
||||||
|
if !exists || len(aliases) == 0 {
|
||||||
|
return "", fmt.Errorf("there is no alias for ID %s", id)
|
||||||
|
}
|
||||||
|
return aliases[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alias gives [id] the alias [alias]
|
||||||
|
func (a Aliaser) Alias(id ID, alias string) error {
|
||||||
|
if _, exists := a.dealias[alias]; exists {
|
||||||
|
return fmt.Errorf("%s is already used as an alias for an ID", alias)
|
||||||
|
}
|
||||||
|
key := id.Key()
|
||||||
|
|
||||||
|
a.dealias[alias] = id
|
||||||
|
a.aliases[key] = append(a.aliases[key], alias)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package ids
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bag is a multiset of IDs.
|
||||||
|
//
|
||||||
|
// A bag has the ability to split and filter on it's bits for ease of use for
|
||||||
|
// binary voting.
|
||||||
|
type Bag struct {
|
||||||
|
counts map[[32]byte]int
|
||||||
|
size int
|
||||||
|
|
||||||
|
mode ID
|
||||||
|
modeFreq int
|
||||||
|
|
||||||
|
threshold int
|
||||||
|
metThreshold Set
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bag) init() {
|
||||||
|
if b.counts == nil {
|
||||||
|
b.counts = make(map[[32]byte]int)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetThreshold sets the number of times an ID must be added to be contained in
|
||||||
|
// the threshold set.
|
||||||
|
func (b *Bag) SetThreshold(threshold int) {
|
||||||
|
if b.threshold == threshold {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b.threshold = threshold
|
||||||
|
b.metThreshold.Clear()
|
||||||
|
for vote, count := range b.counts {
|
||||||
|
if count >= threshold {
|
||||||
|
b.metThreshold.Add(NewID(vote))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add increases the number of times each id has been seen by one.
|
||||||
|
func (b *Bag) Add(ids ...ID) {
|
||||||
|
for _, id := range ids {
|
||||||
|
b.AddCount(id, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCount increases the nubmer of times the id has been seen by count.
|
||||||
|
//
|
||||||
|
// count must be >= 1
|
||||||
|
func (b *Bag) AddCount(id ID, count int) {
|
||||||
|
b.init()
|
||||||
|
|
||||||
|
totalCount := b.counts[*id.ID] + count
|
||||||
|
b.counts[*id.ID] = totalCount
|
||||||
|
b.size += count
|
||||||
|
|
||||||
|
if totalCount > b.modeFreq {
|
||||||
|
b.mode = id
|
||||||
|
b.modeFreq = totalCount
|
||||||
|
}
|
||||||
|
if totalCount >= b.threshold {
|
||||||
|
b.metThreshold.Add(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count returns the number of times the id has been added.
|
||||||
|
func (b *Bag) Count(id ID) int { return b.counts[*id.ID] }
|
||||||
|
|
||||||
|
// Len returns the number of times an id has been added.
|
||||||
|
func (b *Bag) Len() int { return b.size }
|
||||||
|
|
||||||
|
// List returns a list of all ids that have been added.
|
||||||
|
func (b *Bag) List() []ID {
|
||||||
|
idList := []ID(nil)
|
||||||
|
for id := range b.counts {
|
||||||
|
idList = append(idList, NewID(id))
|
||||||
|
}
|
||||||
|
return idList
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode returns the id that has been seen the most and the number of times it
|
||||||
|
// has been seen. Ties are broken by the first id to be seen the reported number
|
||||||
|
// of times.
|
||||||
|
func (b *Bag) Mode() (ID, int) { return b.mode, b.modeFreq }
|
||||||
|
|
||||||
|
// Threshold returns the ids that have been seen at least threshold times.
|
||||||
|
func (b *Bag) Threshold() Set { return b.metThreshold }
|
||||||
|
|
||||||
|
// Filter returns the bag of ids with the same counts as this bag, except all
|
||||||
|
// the ids in the returned bag must have the same bits in the range [start, end]
|
||||||
|
// as id.
|
||||||
|
func (b *Bag) Filter(start, end int, id ID) Bag {
|
||||||
|
newBag := Bag{}
|
||||||
|
for vote, count := range b.counts {
|
||||||
|
voteID := NewID(vote)
|
||||||
|
if EqualSubset(start, end, id, voteID) {
|
||||||
|
newBag.AddCount(voteID, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newBag
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split returns the bags of ids with the same counts a this bag, except all ids
|
||||||
|
// in the 0th index have a 0 at bit [index], and all ids in the 1st index have a
|
||||||
|
// 1 at bit [index].
|
||||||
|
func (b *Bag) Split(index uint) [2]Bag {
|
||||||
|
splitVotes := [2]Bag{}
|
||||||
|
for vote, count := range b.counts {
|
||||||
|
voteID := NewID(vote)
|
||||||
|
bit := voteID.Bit(index)
|
||||||
|
splitVotes[bit].AddCount(voteID, count)
|
||||||
|
}
|
||||||
|
return splitVotes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bag) String() string {
|
||||||
|
sb := strings.Builder{}
|
||||||
|
|
||||||
|
sb.WriteString(fmt.Sprintf("Bag: (Size = %d)", b.Len()))
|
||||||
|
for idBytes, count := range b.counts {
|
||||||
|
id := NewID(idBytes)
|
||||||
|
sb.WriteString(fmt.Sprintf("\n ID[%s]: Count = %d", id, count))
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
|
@ -0,0 +1,201 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package ids
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBagAdd(t *testing.T) {
|
||||||
|
id0 := Empty
|
||||||
|
id1 := NewID([32]byte{1})
|
||||||
|
|
||||||
|
bag := Bag{}
|
||||||
|
|
||||||
|
if count := bag.Count(id0); count != 0 {
|
||||||
|
t.Fatalf("Bag.Count returned %d expected %d", count, 0)
|
||||||
|
} else if count := bag.Count(id1); count != 0 {
|
||||||
|
t.Fatalf("Bag.Count returned %d expected %d", count, 0)
|
||||||
|
} else if size := bag.Len(); size != 0 {
|
||||||
|
t.Fatalf("Bag.Len returned %d expected %d", count, 0)
|
||||||
|
} else if list := bag.List(); list != nil {
|
||||||
|
t.Fatalf("Bag.List returned %v expected %v", list, nil)
|
||||||
|
} else if mode, freq := bag.Mode(); !mode.IsZero() {
|
||||||
|
t.Fatalf("Bag.Mode[0] returned %s expected %s", mode, ID{})
|
||||||
|
} else if freq != 0 {
|
||||||
|
t.Fatalf("Bag.Mode[1] returned %d expected %d", freq, 0)
|
||||||
|
} else if threshold := bag.Threshold(); threshold.Len() != 0 {
|
||||||
|
t.Fatalf("Bag.Threshold returned %s expected %s", threshold, Set{})
|
||||||
|
}
|
||||||
|
|
||||||
|
bag.Add(id0)
|
||||||
|
|
||||||
|
if count := bag.Count(id0); count != 1 {
|
||||||
|
t.Fatalf("Bag.Count returned %d expected %d", count, 1)
|
||||||
|
} else if count := bag.Count(id1); count != 0 {
|
||||||
|
t.Fatalf("Bag.Count returned %d expected %d", count, 0)
|
||||||
|
} else if size := bag.Len(); size != 1 {
|
||||||
|
t.Fatalf("Bag.Len returned %d expected %d", count, 1)
|
||||||
|
} else if list := bag.List(); len(list) != 1 {
|
||||||
|
t.Fatalf("Bag.List returned %d expected %d", len(list), 1)
|
||||||
|
} else if mode, freq := bag.Mode(); !mode.Equals(id0) {
|
||||||
|
t.Fatalf("Bag.Mode[0] returned %s expected %s", mode, id0)
|
||||||
|
} else if freq != 1 {
|
||||||
|
t.Fatalf("Bag.Mode[1] returned %d expected %d", freq, 1)
|
||||||
|
} else if threshold := bag.Threshold(); threshold.Len() != 1 {
|
||||||
|
t.Fatalf("Bag.Threshold returned %d expected %d", len(threshold), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
bag.Add(id0)
|
||||||
|
|
||||||
|
if count := bag.Count(id0); count != 2 {
|
||||||
|
t.Fatalf("Bag.Count returned %d expected %d", count, 2)
|
||||||
|
} else if count := bag.Count(id1); count != 0 {
|
||||||
|
t.Fatalf("Bag.Count returned %d expected %d", count, 0)
|
||||||
|
} else if size := bag.Len(); size != 2 {
|
||||||
|
t.Fatalf("Bag.Len returned %d expected %d", count, 2)
|
||||||
|
} else if list := bag.List(); len(list) != 1 {
|
||||||
|
t.Fatalf("Bag.List returned %d expected %d", len(list), 1)
|
||||||
|
} else if mode, freq := bag.Mode(); !mode.Equals(id0) {
|
||||||
|
t.Fatalf("Bag.Mode[0] returned %s expected %s", mode, id0)
|
||||||
|
} else if freq != 2 {
|
||||||
|
t.Fatalf("Bag.Mode[1] returned %d expected %d", freq, 2)
|
||||||
|
} else if threshold := bag.Threshold(); threshold.Len() != 1 {
|
||||||
|
t.Fatalf("Bag.Threshold returned %d expected %d", len(threshold), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
bag.AddCount(id1, 3)
|
||||||
|
|
||||||
|
if count := bag.Count(id0); count != 2 {
|
||||||
|
t.Fatalf("Bag.Count returned %d expected %d", count, 2)
|
||||||
|
} else if count := bag.Count(id1); count != 3 {
|
||||||
|
t.Fatalf("Bag.Count returned %d expected %d", count, 3)
|
||||||
|
} else if size := bag.Len(); size != 5 {
|
||||||
|
t.Fatalf("Bag.Len returned %d expected %d", count, 5)
|
||||||
|
} else if list := bag.List(); len(list) != 2 {
|
||||||
|
t.Fatalf("Bag.List returned %d expected %d", len(list), 2)
|
||||||
|
} else if mode, freq := bag.Mode(); !mode.Equals(id1) {
|
||||||
|
t.Fatalf("Bag.Mode[0] returned %s expected %s", mode, id1)
|
||||||
|
} else if freq != 3 {
|
||||||
|
t.Fatalf("Bag.Mode[1] returned %d expected %d", freq, 3)
|
||||||
|
} else if threshold := bag.Threshold(); threshold.Len() != 2 {
|
||||||
|
t.Fatalf("Bag.Threshold returned %d expected %d", len(threshold), 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBagSetThreshold(t *testing.T) {
|
||||||
|
id0 := Empty
|
||||||
|
id1 := NewID([32]byte{1})
|
||||||
|
|
||||||
|
bag := Bag{}
|
||||||
|
|
||||||
|
bag.AddCount(id0, 2)
|
||||||
|
bag.AddCount(id1, 3)
|
||||||
|
|
||||||
|
bag.SetThreshold(0)
|
||||||
|
|
||||||
|
if count := bag.Count(id0); count != 2 {
|
||||||
|
t.Fatalf("Bag.Count returned %d expected %d", count, 2)
|
||||||
|
} else if count := bag.Count(id1); count != 3 {
|
||||||
|
t.Fatalf("Bag.Count returned %d expected %d", count, 3)
|
||||||
|
} else if size := bag.Len(); size != 5 {
|
||||||
|
t.Fatalf("Bag.Len returned %d expected %d", count, 5)
|
||||||
|
} else if list := bag.List(); len(list) != 2 {
|
||||||
|
t.Fatalf("Bag.List returned %d expected %d", len(list), 2)
|
||||||
|
} else if mode, freq := bag.Mode(); !mode.Equals(id1) {
|
||||||
|
t.Fatalf("Bag.Mode[0] returned %s expected %s", mode, id1)
|
||||||
|
} else if freq != 3 {
|
||||||
|
t.Fatalf("Bag.Mode[1] returned %d expected %d", freq, 3)
|
||||||
|
} else if threshold := bag.Threshold(); threshold.Len() != 2 {
|
||||||
|
t.Fatalf("Bag.Threshold returned %d expected %d", len(threshold), 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
bag.SetThreshold(3)
|
||||||
|
|
||||||
|
if count := bag.Count(id0); count != 2 {
|
||||||
|
t.Fatalf("Bag.Count returned %d expected %d", count, 2)
|
||||||
|
} else if count := bag.Count(id1); count != 3 {
|
||||||
|
t.Fatalf("Bag.Count returned %d expected %d", count, 3)
|
||||||
|
} else if size := bag.Len(); size != 5 {
|
||||||
|
t.Fatalf("Bag.Len returned %d expected %d", count, 5)
|
||||||
|
} else if list := bag.List(); len(list) != 2 {
|
||||||
|
t.Fatalf("Bag.List returned %d expected %d", len(list), 2)
|
||||||
|
} else if mode, freq := bag.Mode(); !mode.Equals(id1) {
|
||||||
|
t.Fatalf("Bag.Mode[0] returned %s expected %s", mode, id1)
|
||||||
|
} else if freq != 3 {
|
||||||
|
t.Fatalf("Bag.Mode[1] returned %d expected %d", freq, 3)
|
||||||
|
} else if threshold := bag.Threshold(); threshold.Len() != 1 {
|
||||||
|
t.Fatalf("Bag.Threshold returned %d expected %d", len(threshold), 1)
|
||||||
|
} else if !threshold.Contains(id1) {
|
||||||
|
t.Fatalf("Bag.Threshold doesn't contain %s", id1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBagFilter(t *testing.T) {
|
||||||
|
id0 := Empty
|
||||||
|
id1 := NewID([32]byte{1})
|
||||||
|
id2 := NewID([32]byte{2})
|
||||||
|
|
||||||
|
bag := Bag{}
|
||||||
|
|
||||||
|
bag.AddCount(id0, 1)
|
||||||
|
bag.AddCount(id1, 3)
|
||||||
|
bag.AddCount(id2, 5)
|
||||||
|
|
||||||
|
even := bag.Filter(0, 1, id0)
|
||||||
|
|
||||||
|
if count := even.Count(id0); count != 1 {
|
||||||
|
t.Fatalf("Bag.Count returned %d expected %d", count, 1)
|
||||||
|
} else if count := even.Count(id1); count != 0 {
|
||||||
|
t.Fatalf("Bag.Count returned %d expected %d", count, 0)
|
||||||
|
} else if count := even.Count(id2); count != 5 {
|
||||||
|
t.Fatalf("Bag.Count returned %d expected %d", count, 5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBagSplit(t *testing.T) {
|
||||||
|
id0 := Empty
|
||||||
|
id1 := NewID([32]byte{1})
|
||||||
|
id2 := NewID([32]byte{2})
|
||||||
|
|
||||||
|
bag := Bag{}
|
||||||
|
|
||||||
|
bag.AddCount(id0, 1)
|
||||||
|
bag.AddCount(id1, 3)
|
||||||
|
bag.AddCount(id2, 5)
|
||||||
|
|
||||||
|
bags := bag.Split(0)
|
||||||
|
|
||||||
|
evens := bags[0]
|
||||||
|
odds := bags[1]
|
||||||
|
|
||||||
|
if count := evens.Count(id0); count != 1 {
|
||||||
|
t.Fatalf("Bag.Count returned %d expected %d", count, 1)
|
||||||
|
} else if count := evens.Count(id1); count != 0 {
|
||||||
|
t.Fatalf("Bag.Count returned %d expected %d", count, 0)
|
||||||
|
} else if count := evens.Count(id2); count != 5 {
|
||||||
|
t.Fatalf("Bag.Count returned %d expected %d", count, 5)
|
||||||
|
} else if count := odds.Count(id0); count != 0 {
|
||||||
|
t.Fatalf("Bag.Count returned %d expected %d", count, 0)
|
||||||
|
} else if count := odds.Count(id1); count != 3 {
|
||||||
|
t.Fatalf("Bag.Count returned %d expected %d", count, 3)
|
||||||
|
} else if count := odds.Count(id2); count != 0 {
|
||||||
|
t.Fatalf("Bag.Count returned %d expected %d", count, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBagString(t *testing.T) {
|
||||||
|
id0 := Empty
|
||||||
|
|
||||||
|
bag := Bag{}
|
||||||
|
|
||||||
|
bag.AddCount(id0, 1337)
|
||||||
|
|
||||||
|
expected := "Bag: (Size = 1337)\n" +
|
||||||
|
" ID[11111111111111111111111111111111LpoYY]: Count = 1337"
|
||||||
|
|
||||||
|
if bagString := bag.String(); bagString != expected {
|
||||||
|
t.Fatalf("Bag.String:\nReturned:\n%s\nExpected:\n%s", bagString, expected)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package ids
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BitSet is a set that can contain uints in the range [0, 64). All functions
|
||||||
|
// are O(1). The zero value is the empty set.
|
||||||
|
type BitSet uint64
|
||||||
|
|
||||||
|
// Add [i] to the set of ints
|
||||||
|
func (bs *BitSet) Add(i uint) { *bs |= 1 << i }
|
||||||
|
|
||||||
|
// Union adds all the elements in [s] to this set
|
||||||
|
func (bs *BitSet) Union(s BitSet) { *bs |= s }
|
||||||
|
|
||||||
|
// Intersection takes the intersection of [s] with this set
|
||||||
|
func (bs *BitSet) Intersection(s BitSet) { *bs &= s }
|
||||||
|
|
||||||
|
// Difference removes all the elements in [s] from this set
|
||||||
|
func (bs *BitSet) Difference(s BitSet) { *bs &^= s }
|
||||||
|
|
||||||
|
// Remove [i] from the set of ints
|
||||||
|
func (bs *BitSet) Remove(i uint) { *bs &^= 1 << i }
|
||||||
|
|
||||||
|
// Clear removes all elements from this set
|
||||||
|
func (bs *BitSet) Clear() { *bs = 0 }
|
||||||
|
|
||||||
|
// Contains returns true if [i] was previously added to this set
|
||||||
|
func (bs BitSet) Contains(i uint) bool { return bs&(1<<i) != 0 }
|
||||||
|
|
||||||
|
// Len returns the number of elements in this set
|
||||||
|
func (bs BitSet) Len() int { return bits.OnesCount64(uint64(bs)) }
|
||||||
|
|
||||||
|
func (bs BitSet) String() string { return fmt.Sprintf("%016x", uint64(bs)) }
|
|
@ -0,0 +1,157 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package ids
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestBitSet(t *testing.T) {
|
||||||
|
var bs1 BitSet
|
||||||
|
|
||||||
|
if bs1.Len() != 0 {
|
||||||
|
t.Fatalf("Empty set's len should be 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
bs1.Add(5)
|
||||||
|
if bs1.Len() != 1 {
|
||||||
|
t.Fatalf("Wrong set length")
|
||||||
|
} else if !bs1.Contains(5) {
|
||||||
|
t.Fatalf("Set should contain element")
|
||||||
|
}
|
||||||
|
|
||||||
|
bs1.Add(10)
|
||||||
|
if bs1.Len() != 2 {
|
||||||
|
t.Fatalf("Wrong set length")
|
||||||
|
} else if !bs1.Contains(5) {
|
||||||
|
t.Fatalf("Set should contain element")
|
||||||
|
} else if !bs1.Contains(10) {
|
||||||
|
t.Fatalf("Set should contain element")
|
||||||
|
}
|
||||||
|
|
||||||
|
bs1.Add(10)
|
||||||
|
if bs1.Len() != 2 {
|
||||||
|
t.Fatalf("Wrong set length")
|
||||||
|
} else if !bs1.Contains(5) {
|
||||||
|
t.Fatalf("Set should contain element")
|
||||||
|
} else if !bs1.Contains(10) {
|
||||||
|
t.Fatalf("Set should contain element")
|
||||||
|
}
|
||||||
|
|
||||||
|
var bs2 BitSet
|
||||||
|
|
||||||
|
bs2.Add(0)
|
||||||
|
if bs2.Len() != 1 {
|
||||||
|
t.Fatalf("Wrong set length")
|
||||||
|
} else if !bs2.Contains(0) {
|
||||||
|
t.Fatalf("Set should contain element")
|
||||||
|
}
|
||||||
|
|
||||||
|
bs2.Union(bs1)
|
||||||
|
if bs1.Len() != 2 {
|
||||||
|
t.Fatalf("Wrong set length")
|
||||||
|
} else if !bs1.Contains(5) {
|
||||||
|
t.Fatalf("Set should contain element")
|
||||||
|
} else if !bs1.Contains(10) {
|
||||||
|
t.Fatalf("Set should contain element")
|
||||||
|
} else if bs2.Len() != 3 {
|
||||||
|
t.Fatalf("Wrong set length")
|
||||||
|
} else if !bs2.Contains(0) {
|
||||||
|
t.Fatalf("Set should contain element")
|
||||||
|
} else if !bs2.Contains(5) {
|
||||||
|
t.Fatalf("Set should contain element")
|
||||||
|
} else if !bs2.Contains(10) {
|
||||||
|
t.Fatalf("Set should contain element")
|
||||||
|
}
|
||||||
|
|
||||||
|
bs1.Clear()
|
||||||
|
if bs1.Len() != 0 {
|
||||||
|
t.Fatalf("Wrong set length")
|
||||||
|
} else if bs2.Len() != 3 {
|
||||||
|
t.Fatalf("Wrong set length")
|
||||||
|
} else if !bs2.Contains(0) {
|
||||||
|
t.Fatalf("Set should contain element")
|
||||||
|
} else if !bs2.Contains(5) {
|
||||||
|
t.Fatalf("Set should contain element")
|
||||||
|
} else if !bs2.Contains(10) {
|
||||||
|
t.Fatalf("Set should contain element")
|
||||||
|
}
|
||||||
|
|
||||||
|
bs1.Add(63)
|
||||||
|
if bs1.Len() != 1 {
|
||||||
|
t.Fatalf("Wrong set length")
|
||||||
|
} else if !bs1.Contains(63) {
|
||||||
|
t.Fatalf("Set should contain element")
|
||||||
|
}
|
||||||
|
|
||||||
|
bs1.Add(1)
|
||||||
|
if bs1.Len() != 2 {
|
||||||
|
t.Fatalf("Wrong set length")
|
||||||
|
} else if !bs1.Contains(1) {
|
||||||
|
t.Fatalf("Set should contain element")
|
||||||
|
} else if !bs1.Contains(63) {
|
||||||
|
t.Fatalf("Set should contain element")
|
||||||
|
}
|
||||||
|
|
||||||
|
bs1.Remove(63)
|
||||||
|
if bs1.Len() != 1 {
|
||||||
|
t.Fatalf("Wrong set length")
|
||||||
|
} else if !bs1.Contains(1) {
|
||||||
|
t.Fatalf("Set should contain element")
|
||||||
|
}
|
||||||
|
|
||||||
|
var bs3 BitSet
|
||||||
|
|
||||||
|
bs3.Add(0)
|
||||||
|
bs3.Add(2)
|
||||||
|
bs3.Add(5)
|
||||||
|
|
||||||
|
var bs4 BitSet
|
||||||
|
|
||||||
|
bs4.Add(2)
|
||||||
|
bs4.Add(5)
|
||||||
|
|
||||||
|
bs3.Intersection(bs4)
|
||||||
|
|
||||||
|
if bs3.Len() != 2 {
|
||||||
|
t.Fatalf("Wrong set length")
|
||||||
|
} else if !bs3.Contains(2) {
|
||||||
|
t.Fatalf("Set should contain element")
|
||||||
|
} else if !bs3.Contains(5) {
|
||||||
|
t.Fatalf("Set should contain element")
|
||||||
|
} else if bs4.Len() != 2 {
|
||||||
|
t.Fatalf("Wrong set length")
|
||||||
|
}
|
||||||
|
|
||||||
|
var bs5 BitSet
|
||||||
|
|
||||||
|
bs5.Add(7)
|
||||||
|
bs5.Add(11)
|
||||||
|
bs5.Add(9)
|
||||||
|
|
||||||
|
var bs6 BitSet
|
||||||
|
|
||||||
|
bs6.Add(9)
|
||||||
|
bs6.Add(11)
|
||||||
|
|
||||||
|
bs5.Difference(bs6)
|
||||||
|
|
||||||
|
if bs5.Len() != 1 {
|
||||||
|
t.Fatalf("Wrong set length")
|
||||||
|
} else if !bs5.Contains(7) {
|
||||||
|
t.Fatalf("Set should contain element")
|
||||||
|
} else if bs6.Len() != 2 {
|
||||||
|
t.Fatalf("Wrong set length")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitSetString(t *testing.T) {
|
||||||
|
var bs BitSet
|
||||||
|
|
||||||
|
bs.Add(17)
|
||||||
|
|
||||||
|
expected := "0000000000020000"
|
||||||
|
|
||||||
|
if bsString := bs.String(); bsString != expected {
|
||||||
|
t.Fatalf("BitSet.String returned %s expected %s", bsString, expected)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package ids
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NumBits is the number of bits this patricia tree manages
|
||||||
|
const NumBits = 256
|
||||||
|
|
||||||
|
// BitsPerByte is the number of bits per byte
|
||||||
|
const BitsPerByte = 8
|
||||||
|
|
||||||
|
// EqualSubset takes in two indices and two ids and returns if the ids are
|
||||||
|
// equal from bit start to bit end (non-inclusive). Bit indices are defined as:
|
||||||
|
// [7 6 5 4 3 2 1 0] [15 14 13 12 11 10 9 8] ... [255 254 253 252 251 250 249 248]
|
||||||
|
// Where index 7 is the MSB of byte 0.
|
||||||
|
func EqualSubset(start, stop int, id1, id2 ID) bool {
|
||||||
|
stop--
|
||||||
|
if start > stop || stop < 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if stop >= NumBits {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
id1Bytes := id1.Bytes()
|
||||||
|
id2Bytes := id2.Bytes()
|
||||||
|
|
||||||
|
startIndex := start / BitsPerByte
|
||||||
|
stopIndex := stop / BitsPerByte
|
||||||
|
|
||||||
|
// If there is a series of bytes between the first byte and the last byte, they must be equal
|
||||||
|
if startIndex+1 < stopIndex && !bytes.Equal(id1Bytes[startIndex+1:stopIndex], id2Bytes[startIndex+1:stopIndex]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
startBit := uint(start % BitsPerByte) // Index in the byte that the first bit is at
|
||||||
|
stopBit := uint(stop % BitsPerByte) // Index in the byte that the last bit is at
|
||||||
|
|
||||||
|
startMask := -1 << startBit // 111...0... The number of 0s is equal to startBit
|
||||||
|
stopMask := (1 << (stopBit + 1)) - 1 // 000...1... The number of 1s is equal to stopBit+1
|
||||||
|
|
||||||
|
if startIndex == stopIndex {
|
||||||
|
// If we are looking at the same byte, both masks need to be applied
|
||||||
|
mask := startMask & stopMask
|
||||||
|
|
||||||
|
// The index here could be startIndex or stopIndex, as they are equal
|
||||||
|
b1 := mask & int(id1Bytes[startIndex])
|
||||||
|
b2 := mask & int(id2Bytes[startIndex])
|
||||||
|
|
||||||
|
return b1 == b2
|
||||||
|
}
|
||||||
|
|
||||||
|
start1 := startMask & int(id1Bytes[startIndex])
|
||||||
|
start2 := startMask & int(id2Bytes[startIndex])
|
||||||
|
|
||||||
|
stop1 := stopMask & int(id1Bytes[stopIndex])
|
||||||
|
stop2 := stopMask & int(id2Bytes[stopIndex])
|
||||||
|
|
||||||
|
return start1 == start2 && stop1 == stop2
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstDifferenceSubset takes in two indices and two ids and returns the index
|
||||||
|
// of the first difference between the ids inside bit start to bit end
|
||||||
|
// (non-inclusive). Bit indices are defined above
|
||||||
|
func FirstDifferenceSubset(start, stop int, id1, id2 ID) (int, bool) {
|
||||||
|
stop--
|
||||||
|
if start > stop || stop < 0 || stop >= NumBits {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
id1Bytes := id1.Bytes()
|
||||||
|
id2Bytes := id2.Bytes()
|
||||||
|
|
||||||
|
startIndex := start / BitsPerByte
|
||||||
|
stopIndex := stop / BitsPerByte
|
||||||
|
|
||||||
|
startBit := uint(start % BitsPerByte) // Index in the byte that the first bit is at
|
||||||
|
stopBit := uint(stop % BitsPerByte) // Index in the byte that the last bit is at
|
||||||
|
|
||||||
|
startMask := -1 << startBit // 111...0... The number of 0s is equal to startBit
|
||||||
|
stopMask := (1 << (stopBit + 1)) - 1 // 000...1... The number of 1s is equal to stopBit+1
|
||||||
|
|
||||||
|
if startIndex == stopIndex {
|
||||||
|
// If we are looking at the same byte, both masks need to be applied
|
||||||
|
mask := startMask & stopMask
|
||||||
|
|
||||||
|
// The index here could be startIndex or stopIndex, as they are equal
|
||||||
|
b1 := mask & int(id1Bytes[startIndex])
|
||||||
|
b2 := mask & int(id2Bytes[startIndex])
|
||||||
|
|
||||||
|
if b1 == b2 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
bd := b1 ^ b2
|
||||||
|
return bits.TrailingZeros8(uint8(bd)) + startIndex*BitsPerByte, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the first byte, may have some bits masked
|
||||||
|
start1 := startMask & int(id1Bytes[startIndex])
|
||||||
|
start2 := startMask & int(id2Bytes[startIndex])
|
||||||
|
|
||||||
|
if start1 != start2 {
|
||||||
|
bd := start1 ^ start2
|
||||||
|
return bits.TrailingZeros8(uint8(bd)) + startIndex*BitsPerByte, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check all the interior bits
|
||||||
|
for i := startIndex + 1; i < stopIndex; i++ {
|
||||||
|
b1 := int(id1Bytes[i])
|
||||||
|
b2 := int(id2Bytes[i])
|
||||||
|
if b1 != b2 {
|
||||||
|
bd := b1 ^ b2
|
||||||
|
return bits.TrailingZeros8(uint8(bd)) + i*BitsPerByte, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the last byte, may have some bits masked
|
||||||
|
stop1 := stopMask & int(id1Bytes[stopIndex])
|
||||||
|
stop2 := stopMask & int(id2Bytes[stopIndex])
|
||||||
|
|
||||||
|
if stop1 != stop2 {
|
||||||
|
bd := stop1 ^ stop2
|
||||||
|
return bits.TrailingZeros8(uint8(bd)) + stopIndex*BitsPerByte, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// No difference was found
|
||||||
|
return 0, false
|
||||||
|
}
|
|
@ -0,0 +1,181 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package ids
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/utils/random"
|
||||||
|
)
|
||||||
|
|
||||||
|
func flip(b uint8) uint8 {
|
||||||
|
b = b>>4 | b<<4
|
||||||
|
b = (b&0xCC)>>2 | (b&0x33)<<2
|
||||||
|
b = (b&0xAA)>>1 | (b&0x55)<<1
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func BitString(id ID) string {
|
||||||
|
sb := strings.Builder{}
|
||||||
|
for _, b := range id.Bytes() {
|
||||||
|
sb.WriteString(fmt.Sprintf("%08b", flip(b)))
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Check(start, stop int, id1, id2 ID) bool {
|
||||||
|
s1 := BitString(id1)
|
||||||
|
s2 := BitString(id2)
|
||||||
|
|
||||||
|
shorts1 := s1[start:stop]
|
||||||
|
shorts2 := s2[start:stop]
|
||||||
|
|
||||||
|
return shorts1 == shorts2
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEqualSubsetEarlyStop(t *testing.T) {
|
||||||
|
id1 := NewID([32]byte{0xf0, 0x0f})
|
||||||
|
id2 := NewID([32]byte{0xf0, 0x1f})
|
||||||
|
|
||||||
|
if !EqualSubset(0, 12, id1, id2) {
|
||||||
|
t.Fatalf("Should have passed: %08b %08b == %08b %08b", id1.Bytes()[0], id1.Bytes()[1], id2.Bytes()[0], id2.Bytes()[1])
|
||||||
|
} else if EqualSubset(0, 13, id1, id2) {
|
||||||
|
t.Fatalf("Should not have passed: %08b %08b == %08b %08b", id1.Bytes()[0], id1.Bytes()[1], id2.Bytes()[0], id2.Bytes()[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEqualSubsetLateStart(t *testing.T) {
|
||||||
|
id1 := NewID([32]byte{0x1f, 0xf8})
|
||||||
|
id2 := NewID([32]byte{0x10, 0x08})
|
||||||
|
|
||||||
|
if !EqualSubset(4, 12, id1, id2) {
|
||||||
|
t.Fatalf("Should have passed: %08b %08b == %08b %08b", id1.Bytes()[0], id1.Bytes()[1], id2.Bytes()[0], id2.Bytes()[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEqualSubsetSameByte(t *testing.T) {
|
||||||
|
id1 := NewID([32]byte{0x18})
|
||||||
|
id2 := NewID([32]byte{0xfc})
|
||||||
|
|
||||||
|
if !EqualSubset(3, 5, id1, id2) {
|
||||||
|
t.Fatalf("Should have passed: %08b == %08b", id1.Bytes()[0], id2.Bytes()[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEqualSubsetBadMiddle(t *testing.T) {
|
||||||
|
id1 := NewID([32]byte{0x18, 0xe8, 0x55})
|
||||||
|
id2 := NewID([32]byte{0x18, 0x8e, 0x55})
|
||||||
|
|
||||||
|
if EqualSubset(0, 8*3, id1, id2) {
|
||||||
|
t.Fatalf("Should not have passed: %08b == %08b", id1.Bytes()[1], id2.Bytes()[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEqualSubsetAll3Bytes(t *testing.T) {
|
||||||
|
seed := random.Rand(0, math.MaxInt64)
|
||||||
|
id1 := NewID([32]byte{}).Prefix(uint64(seed))
|
||||||
|
bytes1 := id1.Bytes()
|
||||||
|
|
||||||
|
for i := 0; i < BitsPerByte; i++ {
|
||||||
|
for j := i; j < BitsPerByte; j++ {
|
||||||
|
for k := j; k < BitsPerByte; k++ {
|
||||||
|
id2 := NewID([32]byte{uint8(i), uint8(j), uint8(k)})
|
||||||
|
bytes2 := id2.Bytes()
|
||||||
|
|
||||||
|
for start := 0; start < BitsPerByte*3; start++ {
|
||||||
|
for end := start; end <= BitsPerByte*3; end++ {
|
||||||
|
if EqualSubset(start, end, id1, id2) != Check(start, end, id1, id2) {
|
||||||
|
t.Fatalf("Subset failed on seed %d:\ns = %d\ne = %d\n%08b %08b %08b == %08b %08b %08b",
|
||||||
|
seed, start, end,
|
||||||
|
bytes1[0], bytes1[1], bytes1[2],
|
||||||
|
bytes2[0], bytes2[1], bytes2[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEqualSubsetOutOfBounds(t *testing.T) {
|
||||||
|
id1 := NewID([32]byte{0x18, 0xe8, 0x55})
|
||||||
|
id2 := NewID([32]byte{0x18, 0x8e, 0x55})
|
||||||
|
|
||||||
|
if EqualSubset(0, math.MaxInt32, id1, id2) {
|
||||||
|
t.Fatalf("Should not have passed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFirstDifferenceSubsetEarlyStop(t *testing.T) {
|
||||||
|
id1 := NewID([32]byte{0xf0, 0x0f})
|
||||||
|
id2 := NewID([32]byte{0xf0, 0x1f})
|
||||||
|
|
||||||
|
if _, found := FirstDifferenceSubset(0, 12, id1, id2); found {
|
||||||
|
t.Fatalf("Shouldn't have found a difference: %08b %08b == %08b %08b", id1.Bytes()[0], id1.Bytes()[1], id2.Bytes()[0], id2.Bytes()[1])
|
||||||
|
} else if index, found := FirstDifferenceSubset(0, 13, id1, id2); !found {
|
||||||
|
t.Fatalf("Should have found a difference: %08b %08b == %08b %08b", id1.Bytes()[0], id1.Bytes()[1], id2.Bytes()[0], id2.Bytes()[1])
|
||||||
|
} else if index != 12 {
|
||||||
|
t.Fatalf("Found a difference at index %d expected %d: %08b %08b == %08b %08b", index, 12, id1.Bytes()[0], id1.Bytes()[1], id2.Bytes()[0], id2.Bytes()[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFirstDifferenceEqualByte4(t *testing.T) {
|
||||||
|
id1 := NewID([32]byte{0x10})
|
||||||
|
id2 := NewID([32]byte{0x00})
|
||||||
|
|
||||||
|
if _, found := FirstDifferenceSubset(0, 4, id1, id2); found {
|
||||||
|
t.Fatalf("Shouldn't have found a difference: %08b == %08b", id1.Bytes()[0], id2.Bytes()[0])
|
||||||
|
} else if index, found := FirstDifferenceSubset(0, 5, id1, id2); !found {
|
||||||
|
t.Fatalf("Should have found a difference: %08b == %08b", id1.Bytes()[0], id2.Bytes()[0])
|
||||||
|
} else if index != 4 {
|
||||||
|
t.Fatalf("Found a difference at index %d expected %d: %08b == %08b", index, 4, id1.Bytes()[0], id2.Bytes()[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFirstDifferenceEqualByte5(t *testing.T) {
|
||||||
|
id1 := NewID([32]byte{0x20})
|
||||||
|
id2 := NewID([32]byte{0x00})
|
||||||
|
|
||||||
|
if _, found := FirstDifferenceSubset(0, 5, id1, id2); found {
|
||||||
|
t.Fatalf("Shouldn't have found a difference: %08b == %08b", id1.Bytes()[0], id2.Bytes()[0])
|
||||||
|
} else if index, found := FirstDifferenceSubset(0, 6, id1, id2); !found {
|
||||||
|
t.Fatalf("Should have found a difference: %08b == %08b", id1.Bytes()[0], id2.Bytes()[0])
|
||||||
|
} else if index != 5 {
|
||||||
|
t.Fatalf("Found a difference at index %d expected %d: %08b == %08b", index, 5, id1.Bytes()[0], id2.Bytes()[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFirstDifferenceSubsetMiddle(t *testing.T) {
|
||||||
|
id1 := NewID([32]byte{0xf0, 0x0f, 0x11})
|
||||||
|
id2 := NewID([32]byte{0xf0, 0x1f, 0xff})
|
||||||
|
|
||||||
|
if index, found := FirstDifferenceSubset(0, 24, id1, id2); !found {
|
||||||
|
t.Fatalf("Should have found a difference: %08b %08b %08b == %08b %08b %08b", id1.Bytes()[0], id1.Bytes()[1], id1.Bytes()[2], id2.Bytes()[0], id2.Bytes()[1], id2.Bytes()[2])
|
||||||
|
} else if index != 12 {
|
||||||
|
t.Fatalf("Found a difference at index %d expected %d: %08b %08b %08b == %08b %08b %08b", index, 12, id1.Bytes()[0], id1.Bytes()[1], id1.Bytes()[2], id2.Bytes()[0], id2.Bytes()[1], id2.Bytes()[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFirstDifferenceStartMiddle(t *testing.T) {
|
||||||
|
id1 := NewID([32]byte{0x1f, 0x0f, 0x11})
|
||||||
|
id2 := NewID([32]byte{0x0f, 0x1f, 0xff})
|
||||||
|
|
||||||
|
if index, found := FirstDifferenceSubset(0, 24, id1, id2); !found {
|
||||||
|
t.Fatalf("Should have found a difference: %08b %08b %08b == %08b %08b %08b", id1.Bytes()[0], id1.Bytes()[1], id1.Bytes()[2], id2.Bytes()[0], id2.Bytes()[1], id2.Bytes()[2])
|
||||||
|
} else if index != 4 {
|
||||||
|
t.Fatalf("Found a difference at index %d expected %d: %08b %08b %08b == %08b %08b %08b", index, 4, id1.Bytes()[0], id1.Bytes()[1], id1.Bytes()[2], id2.Bytes()[0], id2.Bytes()[1], id2.Bytes()[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFirstDifferenceVacuous(t *testing.T) {
|
||||||
|
id1 := NewID([32]byte{0xf0, 0x0f, 0x11})
|
||||||
|
id2 := NewID([32]byte{0xf0, 0x1f, 0xff})
|
||||||
|
|
||||||
|
if _, found := FirstDifferenceSubset(0, 0, id1, id2); found {
|
||||||
|
t.Fatalf("Shouldn't have found a difference")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package ids
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/utils"
|
||||||
|
"github.com/ava-labs/gecko/utils/formatting"
|
||||||
|
"github.com/ava-labs/gecko/utils/hashing"
|
||||||
|
"github.com/ava-labs/gecko/utils/wrappers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Empty is a useful all zero value
|
||||||
|
var Empty = ID{ID: &[32]byte{}}
|
||||||
|
|
||||||
|
// ID wraps a 32 byte hash as an identifier
|
||||||
|
// Internal field [ID] should never be modified
|
||||||
|
// from outside ids package
|
||||||
|
type ID struct {
|
||||||
|
ID *[32]byte `serialize:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewID creates an identifer from a 32 byte hash
|
||||||
|
func NewID(id [32]byte) ID { return ID{ID: &id} }
|
||||||
|
|
||||||
|
// ToID attempt to convert a byte slice into an id
|
||||||
|
func ToID(bytes []byte) (ID, error) {
|
||||||
|
addrHash, err := hashing.ToHash256(bytes)
|
||||||
|
return NewID(addrHash), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromString is the inverse of ID.String()
|
||||||
|
func FromString(idStr string) (ID, error) {
|
||||||
|
cb58 := formatting.CB58{}
|
||||||
|
err := cb58.FromString(idStr)
|
||||||
|
if err != nil {
|
||||||
|
return ID{}, err
|
||||||
|
}
|
||||||
|
return ToID(cb58.Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON ...
|
||||||
|
func (id ID) MarshalJSON() ([]byte, error) {
|
||||||
|
if id.IsZero() {
|
||||||
|
return []byte("null"), nil
|
||||||
|
}
|
||||||
|
cb58 := formatting.CB58{Bytes: id.ID[:]}
|
||||||
|
return cb58.MarshalJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON ...
|
||||||
|
func (id *ID) UnmarshalJSON(b []byte) error {
|
||||||
|
if string(b) == "null" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cb58 := formatting.CB58{}
|
||||||
|
if err := cb58.UnmarshalJSON(b); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newID, err := ToID(cb58.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*id = newID
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero returns true if the value has not been initialized
|
||||||
|
func (id ID) IsZero() bool { return id.ID == nil }
|
||||||
|
|
||||||
|
// Key returns a 32 byte hash that this id represents. This is useful to allow
|
||||||
|
// for this id to be used as keys in maps.
|
||||||
|
func (id ID) Key() [32]byte { return *id.ID }
|
||||||
|
|
||||||
|
// Prefix this id to create a more selective id. This can be used to store
|
||||||
|
// multiple values under the same key. For example:
|
||||||
|
// prefix1(id) -> confidence
|
||||||
|
// prefix2(id) -> vertex
|
||||||
|
// This will return a new id and not modify the original id.
|
||||||
|
func (id ID) Prefix(prefixes ...uint64) ID {
|
||||||
|
packer := wrappers.Packer{
|
||||||
|
Bytes: make([]byte, len(prefixes)*wrappers.LongLen+hashing.HashLen),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, prefix := range prefixes {
|
||||||
|
packer.PackLong(prefix)
|
||||||
|
}
|
||||||
|
packer.PackFixedBytes(id.Bytes())
|
||||||
|
|
||||||
|
return NewID(hashing.ComputeHash256Array(packer.Bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals returns true if the ids have the same byte representation
|
||||||
|
func (id ID) Equals(oID ID) bool {
|
||||||
|
return id.ID == oID.ID ||
|
||||||
|
(id.ID != nil && oID.ID != nil && bytes.Equal(id.Bytes(), oID.Bytes()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the 32 byte hash as a slice. It is assumed this slice is not
|
||||||
|
// modified.
|
||||||
|
func (id ID) Bytes() []byte { return id.ID[:] }
|
||||||
|
|
||||||
|
// Bit returns the bit value at the ith index of the byte array. Returns 0 or 1
|
||||||
|
func (id ID) Bit(i uint) int {
|
||||||
|
byteIndex := i / BitsPerByte
|
||||||
|
bitIndex := i % BitsPerByte
|
||||||
|
|
||||||
|
bytes := id.Bytes()
|
||||||
|
b := bytes[byteIndex]
|
||||||
|
|
||||||
|
// b = [7, 6, 5, 4, 3, 2, 1, 0]
|
||||||
|
|
||||||
|
b = b >> bitIndex
|
||||||
|
|
||||||
|
// b = [0, ..., bitIndex + 1, bitIndex]
|
||||||
|
// 1 = [0, 0, 0, 0, 0, 0, 0, 1]
|
||||||
|
|
||||||
|
b = b & 1
|
||||||
|
|
||||||
|
// b = [0, 0, 0, 0, 0, 0, 0, bitIndex]
|
||||||
|
|
||||||
|
return int(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hex returns a hex encoded string of this id.
|
||||||
|
func (id ID) Hex() string { return hex.EncodeToString(id.Bytes()) }
|
||||||
|
|
||||||
|
func (id ID) String() string {
|
||||||
|
if id.IsZero() {
|
||||||
|
return "nil"
|
||||||
|
}
|
||||||
|
bytes := id.Bytes()
|
||||||
|
cb58 := formatting.CB58{Bytes: bytes}
|
||||||
|
return cb58.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortIDData []ID
|
||||||
|
|
||||||
|
func (ids sortIDData) Less(i, j int) bool {
|
||||||
|
return bytes.Compare(
|
||||||
|
ids[i].Bytes(),
|
||||||
|
ids[j].Bytes()) == -1
|
||||||
|
}
|
||||||
|
func (ids sortIDData) Len() int { return len(ids) }
|
||||||
|
func (ids sortIDData) Swap(i, j int) { ids[j], ids[i] = ids[i], ids[j] }
|
||||||
|
|
||||||
|
// SortIDs sorts the ids lexicographically
|
||||||
|
func SortIDs(ids []ID) { sort.Sort(sortIDData(ids)) }
|
||||||
|
|
||||||
|
// IsSortedAndUniqueIDs returns true if the ids are sorted and unique
|
||||||
|
func IsSortedAndUniqueIDs(ids []ID) bool { return utils.IsSortedAndUnique(sortIDData(ids)) }
|
|
@ -0,0 +1,81 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package ids
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestID(t *testing.T) {
|
||||||
|
hash := [32]byte{24}
|
||||||
|
id := NewID(hash)
|
||||||
|
|
||||||
|
if key := id.Key(); !bytes.Equal(hash[:], key[:]) {
|
||||||
|
t.Fatalf("ID.Key returned wrong bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
prefixed := id.Prefix(0)
|
||||||
|
|
||||||
|
if key := id.Key(); !bytes.Equal(hash[:], key[:]) {
|
||||||
|
t.Fatalf("ID.Prefix mutated the ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
if nextPrefix := id.Prefix(0); !prefixed.Equals(nextPrefix) {
|
||||||
|
t.Fatalf("ID.Prefix not consistant")
|
||||||
|
}
|
||||||
|
|
||||||
|
if b := id.Bytes(); !bytes.Equal(hash[:], b) {
|
||||||
|
t.Fatalf("ID.Bytes returned wrong bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
if str := id.String(); str != "Ba3mm8Ra8JYYebeZ9p7zw1ayorDbeD1euwxhgzSLsncKqGoNt" {
|
||||||
|
t.Fatalf("ID.String returned wrong string: %s", str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIDBit(t *testing.T) {
|
||||||
|
id0 := NewID([32]byte{1 << 0})
|
||||||
|
id1 := NewID([32]byte{1 << 1})
|
||||||
|
id2 := NewID([32]byte{1 << 2})
|
||||||
|
id3 := NewID([32]byte{1 << 3})
|
||||||
|
id4 := NewID([32]byte{1 << 4})
|
||||||
|
id5 := NewID([32]byte{1 << 5})
|
||||||
|
id6 := NewID([32]byte{1 << 6})
|
||||||
|
id7 := NewID([32]byte{1 << 7})
|
||||||
|
id8 := NewID([32]byte{0, 1 << 0})
|
||||||
|
|
||||||
|
if id0.Bit(0) != 1 {
|
||||||
|
t.Fatalf("Wrong bit")
|
||||||
|
} else if id1.Bit(1) != 1 {
|
||||||
|
t.Fatalf("Wrong bit")
|
||||||
|
} else if id2.Bit(2) != 1 {
|
||||||
|
t.Fatalf("Wrong bit")
|
||||||
|
} else if id3.Bit(3) != 1 {
|
||||||
|
t.Fatalf("Wrong bit")
|
||||||
|
} else if id4.Bit(4) != 1 {
|
||||||
|
t.Fatalf("Wrong bit")
|
||||||
|
} else if id5.Bit(5) != 1 {
|
||||||
|
t.Fatalf("Wrong bit")
|
||||||
|
} else if id6.Bit(6) != 1 {
|
||||||
|
t.Fatalf("Wrong bit")
|
||||||
|
} else if id7.Bit(7) != 1 {
|
||||||
|
t.Fatalf("Wrong bit")
|
||||||
|
} else if id8.Bit(8) != 1 {
|
||||||
|
t.Fatalf("Wrong bit")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromString(t *testing.T) {
|
||||||
|
key := [32]byte{'a', 'v', 'a', ' ', 'l', 'a', 'b', 's'}
|
||||||
|
id := NewID(key)
|
||||||
|
idStr := id.String()
|
||||||
|
id2, err := FromString(idStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if id.Key() != id2.Key() {
|
||||||
|
t.Fatal("Expected FromString to be inverse of String but it wasn't")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package ids
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
)
|
||||||
|
|
||||||
|
// QueueSet is a set of IDs stored in fifo order
|
||||||
|
type QueueSet struct {
|
||||||
|
idList *list.List
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qs *QueueSet) init() {
|
||||||
|
if qs.idList == nil {
|
||||||
|
qs.idList = list.New()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHead ...
|
||||||
|
func (qs *QueueSet) SetHead(id ID) {
|
||||||
|
qs.init()
|
||||||
|
|
||||||
|
for qs.idList.Len() > 0 {
|
||||||
|
element := qs.idList.Front()
|
||||||
|
head := element.Value.(ID)
|
||||||
|
if head.Equals(id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
qs.idList.Remove(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
qs.idList.PushFront(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append ...
|
||||||
|
func (qs *QueueSet) Append(id ID) {
|
||||||
|
qs.init()
|
||||||
|
|
||||||
|
qs.idList.PushBack(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTail ...
|
||||||
|
func (qs *QueueSet) GetTail() ID {
|
||||||
|
qs.init()
|
||||||
|
|
||||||
|
if qs.idList.Len() == 0 {
|
||||||
|
return ID{}
|
||||||
|
}
|
||||||
|
return qs.idList.Back().Value.(ID)
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package ids
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set is a set of IDs
|
||||||
|
type Set map[[32]byte]bool
|
||||||
|
|
||||||
|
func (ids *Set) init(size int) {
|
||||||
|
if *ids == nil {
|
||||||
|
*ids = make(map[[32]byte]bool, size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all the ids to this set, if the id is already in the set, nothing happens
|
||||||
|
func (ids *Set) Add(idList ...ID) {
|
||||||
|
ids.init(2 * len(idList))
|
||||||
|
for _, id := range idList {
|
||||||
|
(*ids)[*id.ID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Union adds all the ids from the provided sets to this set.
|
||||||
|
func (ids *Set) Union(set Set) {
|
||||||
|
ids.init(2 * set.Len())
|
||||||
|
for id := range set {
|
||||||
|
(*ids)[id] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains returns true if the set contains this id, false otherwise
|
||||||
|
func (ids *Set) Contains(id ID) bool {
|
||||||
|
ids.init(1)
|
||||||
|
return (*ids)[*id.ID]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overlaps returns true if the intersection of the set is non-empty
|
||||||
|
func (ids *Set) Overlaps(big Set) bool {
|
||||||
|
small := *ids
|
||||||
|
if small.Len() > big.Len() {
|
||||||
|
small = big
|
||||||
|
big = *ids
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, id := range small.List() {
|
||||||
|
if big.Contains(id) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of ids in this set
|
||||||
|
func (ids Set) Len() int { return len(ids) }
|
||||||
|
|
||||||
|
// Remove all the id from this set, if the id isn't in the set, nothing happens
|
||||||
|
func (ids *Set) Remove(idList ...ID) {
|
||||||
|
ids.init(1)
|
||||||
|
for _, id := range idList {
|
||||||
|
delete(*ids, *id.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear empties this set
|
||||||
|
func (ids *Set) Clear() { *ids = nil }
|
||||||
|
|
||||||
|
// List converts this set into a list
|
||||||
|
func (ids Set) List() []ID {
|
||||||
|
idList := []ID(nil)
|
||||||
|
for id := range ids {
|
||||||
|
idList = append(idList, NewID(id))
|
||||||
|
}
|
||||||
|
return idList
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals returns true if the sets contain the same elements
|
||||||
|
func (ids Set) Equals(oIDs Set) bool {
|
||||||
|
if ids.Len() != oIDs.Len() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for key := range oIDs {
|
||||||
|
if !ids[key] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of a set
|
||||||
|
func (ids Set) String() string {
|
||||||
|
sb := strings.Builder{}
|
||||||
|
sb.WriteString("{")
|
||||||
|
first := true
|
||||||
|
for idBytes := range ids {
|
||||||
|
if !first {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
}
|
||||||
|
first = false
|
||||||
|
sb.WriteString(NewID(idBytes).String())
|
||||||
|
}
|
||||||
|
sb.WriteString("}")
|
||||||
|
return sb.String()
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package ids
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSet(t *testing.T) {
|
||||||
|
id1 := NewID([32]byte{1})
|
||||||
|
|
||||||
|
ids := Set{}
|
||||||
|
|
||||||
|
ids.Add(id1)
|
||||||
|
if !ids.Contains(id1) {
|
||||||
|
t.Fatalf("Initial value not set correctly")
|
||||||
|
}
|
||||||
|
|
||||||
|
ids.Remove(id1)
|
||||||
|
if ids.Contains(id1) {
|
||||||
|
t.Fatalf("Value not removed correctly")
|
||||||
|
}
|
||||||
|
|
||||||
|
ids.Add(id1)
|
||||||
|
if !ids.Contains(id1) {
|
||||||
|
t.Fatalf("Initial value not set correctly")
|
||||||
|
} else if ids.Len() != 1 {
|
||||||
|
t.Fatalf("Bad set size")
|
||||||
|
} else if list := ids.List(); len(list) != 1 {
|
||||||
|
t.Fatalf("Bad list size")
|
||||||
|
} else if !list[0].Equals(id1) {
|
||||||
|
t.Fatalf("Set value not correct")
|
||||||
|
}
|
||||||
|
|
||||||
|
ids.Clear()
|
||||||
|
if ids.Contains(id1) {
|
||||||
|
t.Fatalf("Value not removed correctly")
|
||||||
|
}
|
||||||
|
|
||||||
|
ids.Add(id1)
|
||||||
|
|
||||||
|
ids2 := Set{}
|
||||||
|
|
||||||
|
if ids.Overlaps(ids2) {
|
||||||
|
t.Fatalf("Empty set shouldn't overlap")
|
||||||
|
}
|
||||||
|
|
||||||
|
ids2.Union(ids)
|
||||||
|
if !ids2.Contains(id1) {
|
||||||
|
t.Fatalf("Value not union added correctly")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ids.Overlaps(ids2) {
|
||||||
|
t.Fatalf("Sets overlap")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package ids
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/utils"
|
||||||
|
"github.com/ava-labs/gecko/utils/formatting"
|
||||||
|
"github.com/ava-labs/gecko/utils/hashing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ShortEmpty is a useful all zero value
|
||||||
|
var ShortEmpty = ShortID{ID: &[20]byte{}}
|
||||||
|
|
||||||
|
// ShortID wraps a 20 byte hash as an identifier
|
||||||
|
type ShortID struct {
|
||||||
|
ID *[20]byte `serialize:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewShortID creates an identifer from a 20 byte hash
|
||||||
|
func NewShortID(id [20]byte) ShortID { return ShortID{ID: &id} }
|
||||||
|
|
||||||
|
// ToShortID attempt to convert a byte slice into an id
|
||||||
|
func ToShortID(bytes []byte) (ShortID, error) {
|
||||||
|
addrHash, err := hashing.ToHash160(bytes)
|
||||||
|
return NewShortID(addrHash), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShortFromString is the inverse of ShortID.String()
|
||||||
|
func ShortFromString(idStr string) (ShortID, error) {
|
||||||
|
cb58 := formatting.CB58{}
|
||||||
|
err := cb58.FromString(idStr)
|
||||||
|
if err != nil {
|
||||||
|
return ShortID{}, err
|
||||||
|
}
|
||||||
|
return ToShortID(cb58.Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON ...
|
||||||
|
func (id ShortID) MarshalJSON() ([]byte, error) {
|
||||||
|
if id.IsZero() {
|
||||||
|
return []byte("null"), nil
|
||||||
|
}
|
||||||
|
cb58 := formatting.CB58{Bytes: id.ID[:]}
|
||||||
|
return cb58.MarshalJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON ...
|
||||||
|
func (id *ShortID) UnmarshalJSON(b []byte) error {
|
||||||
|
if string(b) == "null" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cb58 := formatting.CB58{}
|
||||||
|
if err := cb58.UnmarshalJSON(b); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newID, err := ToShortID(cb58.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*id = newID
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero returns true if the value has not been initialized
|
||||||
|
func (id ShortID) IsZero() bool { return id.ID == nil }
|
||||||
|
|
||||||
|
// LongID returns a 32 byte identifier from this id
|
||||||
|
func (id ShortID) LongID() ID {
|
||||||
|
dest := [32]byte{}
|
||||||
|
copy(dest[:], id.ID[:])
|
||||||
|
return NewID(dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key returns a 20 byte hash that this id represents. This is useful to allow
|
||||||
|
// for this id to be used as keys in maps.
|
||||||
|
func (id ShortID) Key() [20]byte { return *id.ID }
|
||||||
|
|
||||||
|
// Equals returns true if the ids have the same byte representation
|
||||||
|
func (id ShortID) Equals(oID ShortID) bool {
|
||||||
|
return id.ID == oID.ID ||
|
||||||
|
(id.ID != nil && oID.ID != nil && bytes.Equal(id.Bytes(), oID.Bytes()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the 20 byte hash as a slice. It is assumed this slice is not
|
||||||
|
// modified.
|
||||||
|
func (id ShortID) Bytes() []byte { return id.ID[:] }
|
||||||
|
|
||||||
|
// Hex returns a hex encoded string of this id.
|
||||||
|
func (id ShortID) Hex() string { return hex.EncodeToString(id.Bytes()) }
|
||||||
|
|
||||||
|
func (id ShortID) String() string {
|
||||||
|
if id.IsZero() {
|
||||||
|
return "nil"
|
||||||
|
}
|
||||||
|
bytes := id.Bytes()
|
||||||
|
cb58 := formatting.CB58{Bytes: bytes}
|
||||||
|
return cb58.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortShortIDData []ShortID
|
||||||
|
|
||||||
|
func (ids sortShortIDData) Less(i, j int) bool {
|
||||||
|
return bytes.Compare(
|
||||||
|
ids[i].Bytes(),
|
||||||
|
ids[j].Bytes()) == -1
|
||||||
|
}
|
||||||
|
func (ids sortShortIDData) Len() int { return len(ids) }
|
||||||
|
func (ids sortShortIDData) Swap(i, j int) { ids[j], ids[i] = ids[i], ids[j] }
|
||||||
|
|
||||||
|
// SortShortIDs sorts the ids lexicographically
|
||||||
|
func SortShortIDs(ids []ShortID) { sort.Sort(sortShortIDData(ids)) }
|
||||||
|
|
||||||
|
// IsSortedAndUniqueShortIDs returns true if the ids are sorted and unique
|
||||||
|
func IsSortedAndUniqueShortIDs(ids []ShortID) bool {
|
||||||
|
return utils.IsSortedAndUnique(sortShortIDData(ids))
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package ids
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// ShortSet is a set of ShortIDs
|
||||||
|
type ShortSet map[[20]byte]bool
|
||||||
|
|
||||||
|
func (ids *ShortSet) init(size int) {
|
||||||
|
if *ids == nil {
|
||||||
|
*ids = make(map[[20]byte]bool, size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all the ids to this set, if the id is already in the set, nothing happens
|
||||||
|
func (ids *ShortSet) Add(idList ...ShortID) {
|
||||||
|
ids.init(2 * len(idList))
|
||||||
|
for _, id := range idList {
|
||||||
|
(*ids)[id.Key()] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Union adds all the ids from the provided sets to this set.
|
||||||
|
func (ids *ShortSet) Union(idSet ShortSet) {
|
||||||
|
ids.init(2 * idSet.Len())
|
||||||
|
for id := range idSet {
|
||||||
|
(*ids)[id] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains returns true if the set contains this id, false otherwise
|
||||||
|
func (ids *ShortSet) Contains(id ShortID) bool {
|
||||||
|
ids.init(1)
|
||||||
|
return (*ids)[id.Key()]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of ids in this set
|
||||||
|
func (ids ShortSet) Len() int { return len(ids) }
|
||||||
|
|
||||||
|
// Remove all the id from this set, if the id isn't in the set, nothing happens
|
||||||
|
func (ids *ShortSet) Remove(idList ...ShortID) {
|
||||||
|
ids.init(1)
|
||||||
|
for _, id := range idList {
|
||||||
|
delete(*ids, id.Key())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear empties this set
|
||||||
|
func (ids *ShortSet) Clear() { *ids = nil }
|
||||||
|
|
||||||
|
// CappedList returns a list of length at most [size]. Size should be >= 0
|
||||||
|
func (ids ShortSet) CappedList(size int) []ShortID {
|
||||||
|
idList := make([]ShortID, size)[:0]
|
||||||
|
for id := range ids {
|
||||||
|
if size <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
size--
|
||||||
|
idList = append(idList, NewShortID(id))
|
||||||
|
}
|
||||||
|
return idList
|
||||||
|
}
|
||||||
|
|
||||||
|
// List converts this set into a list
|
||||||
|
func (ids ShortSet) List() []ShortID {
|
||||||
|
idList := make([]ShortID, len(ids))[:0]
|
||||||
|
for id := range ids {
|
||||||
|
idList = append(idList, NewShortID(id))
|
||||||
|
}
|
||||||
|
return idList
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals returns true if the sets contain the same elements
|
||||||
|
func (ids ShortSet) Equals(oIDs ShortSet) bool {
|
||||||
|
if ids.Len() != oIDs.Len() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for key := range oIDs {
|
||||||
|
if !ids[key] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of a set
|
||||||
|
func (ids ShortSet) String() string {
|
||||||
|
sb := strings.Builder{}
|
||||||
|
sb.WriteString("{")
|
||||||
|
first := true
|
||||||
|
for idBytes := range ids {
|
||||||
|
if !first {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
}
|
||||||
|
first = false
|
||||||
|
sb.WriteString(NewShortID(idBytes).String())
|
||||||
|
}
|
||||||
|
sb.WriteString("}")
|
||||||
|
return sb.String()
|
||||||
|
}
|
|
@ -0,0 +1,239 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package ids
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShortSetContains(t *testing.T) {
|
||||||
|
set := ShortSet{}
|
||||||
|
|
||||||
|
id0 := NewShortID([20]byte{0})
|
||||||
|
id1 := NewShortID([20]byte{1})
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case set.Contains(id0):
|
||||||
|
t.Fatalf("Sets shouldn't contain %s", id0)
|
||||||
|
case set.Contains(id1):
|
||||||
|
t.Fatalf("Sets shouldn't contain %s", id1)
|
||||||
|
}
|
||||||
|
|
||||||
|
set.Add(id0)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case !set.Contains(id0):
|
||||||
|
t.Fatalf("Set should contain %s", id0)
|
||||||
|
case set.Contains(id1):
|
||||||
|
t.Fatalf("Set shouldn't contain %s", id1)
|
||||||
|
}
|
||||||
|
|
||||||
|
set.Add(id1)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case !set.Contains(id0):
|
||||||
|
t.Fatalf("Set should contain %s", id0)
|
||||||
|
case !set.Contains(id1):
|
||||||
|
t.Fatalf("Set should contain %s", id1)
|
||||||
|
}
|
||||||
|
|
||||||
|
set.Remove(id0)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case set.Contains(id0):
|
||||||
|
t.Fatalf("Sets shouldn't contain %s", id0)
|
||||||
|
case !set.Contains(id1):
|
||||||
|
t.Fatalf("Set should contain %s", id1)
|
||||||
|
}
|
||||||
|
|
||||||
|
set.Add(id0)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case !set.Contains(id0):
|
||||||
|
t.Fatalf("Set should contain %s", id0)
|
||||||
|
case !set.Contains(id1):
|
||||||
|
t.Fatalf("Set should contain %s", id1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortSetUnion(t *testing.T) {
|
||||||
|
set := ShortSet{}
|
||||||
|
unionSet := ShortSet{}
|
||||||
|
|
||||||
|
id0 := NewShortID([20]byte{0})
|
||||||
|
id1 := NewShortID([20]byte{1})
|
||||||
|
|
||||||
|
unionSet.Add(id0)
|
||||||
|
set.Union(unionSet)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case !set.Contains(id0):
|
||||||
|
t.Fatalf("Set should contain %s", id0)
|
||||||
|
case set.Contains(id1):
|
||||||
|
t.Fatalf("Set shouldn't contain %s", id1)
|
||||||
|
}
|
||||||
|
|
||||||
|
unionSet.Add(id1)
|
||||||
|
set.Union(unionSet)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case !set.Contains(id0):
|
||||||
|
t.Fatalf("Set should contain %s", id0)
|
||||||
|
case !set.Contains(id1):
|
||||||
|
t.Fatalf("Set should contain %s", id1)
|
||||||
|
}
|
||||||
|
|
||||||
|
set.Remove(id0)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case set.Contains(id0):
|
||||||
|
t.Fatalf("Sets shouldn't contain %s", id0)
|
||||||
|
case !set.Contains(id1):
|
||||||
|
t.Fatalf("Set should contain %s", id1)
|
||||||
|
}
|
||||||
|
|
||||||
|
set.Clear()
|
||||||
|
set.Union(unionSet)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case !set.Contains(id0):
|
||||||
|
t.Fatalf("Set should contain %s", id0)
|
||||||
|
case !set.Contains(id1):
|
||||||
|
t.Fatalf("Set should contain %s", id1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortSetEquals(t *testing.T) {
|
||||||
|
set := ShortSet{}
|
||||||
|
otherSet := ShortSet{}
|
||||||
|
if !set.Equals(otherSet) {
|
||||||
|
t.Fatal("Empty sets should be equal")
|
||||||
|
}
|
||||||
|
if !otherSet.Equals(set) {
|
||||||
|
t.Fatal("Empty sets should be equal")
|
||||||
|
}
|
||||||
|
|
||||||
|
set.Add(NewShortID([20]byte{1, 2, 3, 4, 5}))
|
||||||
|
if set.Equals(otherSet) {
|
||||||
|
t.Fatal("Sets should be unequal")
|
||||||
|
}
|
||||||
|
if otherSet.Equals(set) {
|
||||||
|
t.Fatal("Sets should be unequal")
|
||||||
|
}
|
||||||
|
|
||||||
|
otherSet.Add(NewShortID([20]byte{1, 2, 3, 4, 5}))
|
||||||
|
if !set.Equals(otherSet) {
|
||||||
|
t.Fatal("sets should be equal")
|
||||||
|
}
|
||||||
|
if !otherSet.Equals(set) {
|
||||||
|
t.Fatal("sets should be equal")
|
||||||
|
}
|
||||||
|
|
||||||
|
otherSet.Add(NewShortID([20]byte{6, 7, 8, 9, 10}))
|
||||||
|
if set.Equals(otherSet) {
|
||||||
|
t.Fatal("Sets should be unequal")
|
||||||
|
}
|
||||||
|
if otherSet.Equals(set) {
|
||||||
|
t.Fatal("Sets should be unequal")
|
||||||
|
}
|
||||||
|
|
||||||
|
set.Add(NewShortID([20]byte{6, 7, 8, 9, 10}))
|
||||||
|
if !set.Equals(otherSet) {
|
||||||
|
t.Fatal("sets should be equal")
|
||||||
|
}
|
||||||
|
if !otherSet.Equals(set) {
|
||||||
|
t.Fatal("sets should be equal")
|
||||||
|
}
|
||||||
|
|
||||||
|
otherSet.Add(NewShortID([20]byte{11, 12, 13, 14, 15}))
|
||||||
|
if set.Equals(otherSet) {
|
||||||
|
t.Fatal("Sets should be unequal")
|
||||||
|
}
|
||||||
|
if otherSet.Equals(set) {
|
||||||
|
t.Fatal("Sets should be unequal")
|
||||||
|
}
|
||||||
|
|
||||||
|
set.Add(NewShortID([20]byte{11, 12, 13, 14, 16}))
|
||||||
|
if set.Equals(otherSet) {
|
||||||
|
t.Fatal("Sets should be unequal")
|
||||||
|
}
|
||||||
|
if otherSet.Equals(set) {
|
||||||
|
t.Fatal("Sets should be unequal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortSetList(t *testing.T) {
|
||||||
|
set := ShortSet{}
|
||||||
|
otherSet := ShortSet{}
|
||||||
|
|
||||||
|
id0 := NewShortID([20]byte{0})
|
||||||
|
id1 := NewShortID([20]byte{1})
|
||||||
|
|
||||||
|
set.Add(id0)
|
||||||
|
otherSet.Add(set.List()...)
|
||||||
|
|
||||||
|
if !set.Equals(otherSet) {
|
||||||
|
t.Fatalf("Sets should be equal but are:\n%s\n%s", set, otherSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
set.Add(id1)
|
||||||
|
otherSet.Clear()
|
||||||
|
otherSet.Add(set.List()...)
|
||||||
|
|
||||||
|
if !set.Equals(otherSet) {
|
||||||
|
t.Fatalf("Sets should be equal but are:\n%s\n%s", set, otherSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortSetCappedList(t *testing.T) {
|
||||||
|
set := ShortSet{}
|
||||||
|
|
||||||
|
id := ShortEmpty
|
||||||
|
|
||||||
|
if list := set.CappedList(0); len(list) != 0 {
|
||||||
|
t.Fatalf("List should have been empty but was %v", list)
|
||||||
|
}
|
||||||
|
|
||||||
|
set.Add(id)
|
||||||
|
|
||||||
|
if list := set.CappedList(0); len(list) != 0 {
|
||||||
|
t.Fatalf("List should have been empty but was %v", list)
|
||||||
|
} else if list := set.CappedList(1); len(list) != 1 {
|
||||||
|
t.Fatalf("List should have had length %d but had %d", 1, len(list))
|
||||||
|
} else if returnedID := list[0]; !id.Equals(returnedID) {
|
||||||
|
t.Fatalf("List should have been %s but was %s", id, returnedID)
|
||||||
|
} else if list := set.CappedList(2); len(list) != 1 {
|
||||||
|
t.Fatalf("List should have had length %d but had %d", 1, len(list))
|
||||||
|
} else if returnedID := list[0]; !id.Equals(returnedID) {
|
||||||
|
t.Fatalf("List should have been %s but was %s", id, returnedID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortSetString(t *testing.T) {
|
||||||
|
set := ShortSet{}
|
||||||
|
|
||||||
|
id0 := NewShortID([20]byte{0})
|
||||||
|
id1 := NewShortID([20]byte{1})
|
||||||
|
|
||||||
|
if str := set.String(); str != "{}" {
|
||||||
|
t.Fatalf("Set should have been %s but was %s", "{}", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
set.Add(id0)
|
||||||
|
|
||||||
|
if str := set.String(); str != "{111111111111111111116DBWJs}" {
|
||||||
|
t.Fatalf("Set should have been %s but was %s", "{111111111111111111116DBWJs}", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
set.Add(id1)
|
||||||
|
|
||||||
|
if str := set.String(); !strings.Contains(str, "111111111111111111116DBWJs") {
|
||||||
|
t.Fatalf("Set should have contained %s", "111111111111111111116DBWJs")
|
||||||
|
} else if !strings.Contains(str, "6HgC8KRBEhXYbF4riJyJFLSHt37UNuRt") {
|
||||||
|
t.Fatalf("Set should have contained %s", "6HgC8KRBEhXYbF4riJyJFLSHt37UNuRt")
|
||||||
|
} else if count := strings.Count(str, ","); count != 1 {
|
||||||
|
t.Fatalf("Should only have one %s in %s", ",", str)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package ids
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UniqueBag ...
|
||||||
|
type UniqueBag map[[32]byte]BitSet
|
||||||
|
|
||||||
|
func (b *UniqueBag) init() {
|
||||||
|
if *b == nil {
|
||||||
|
*b = make(map[[32]byte]BitSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ...
|
||||||
|
func (b *UniqueBag) Add(setID uint, idSet ...ID) {
|
||||||
|
bs := BitSet(0)
|
||||||
|
bs.Add(setID)
|
||||||
|
|
||||||
|
for _, id := range idSet {
|
||||||
|
b.UnionSet(id, bs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnionSet ...
|
||||||
|
func (b *UniqueBag) UnionSet(id ID, set BitSet) {
|
||||||
|
b.init()
|
||||||
|
|
||||||
|
key := id.Key()
|
||||||
|
previousSet := (*b)[key]
|
||||||
|
previousSet.Union(set)
|
||||||
|
(*b)[key] = previousSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// DifferenceSet ...
|
||||||
|
func (b *UniqueBag) DifferenceSet(id ID, set BitSet) {
|
||||||
|
b.init()
|
||||||
|
|
||||||
|
key := id.Key()
|
||||||
|
previousSet := (*b)[key]
|
||||||
|
previousSet.Difference(set)
|
||||||
|
(*b)[key] = previousSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Difference ...
|
||||||
|
func (b *UniqueBag) Difference(diff *UniqueBag) {
|
||||||
|
b.init()
|
||||||
|
|
||||||
|
for key, previousSet := range *b {
|
||||||
|
if previousSetDiff, exists := (*diff)[key]; exists {
|
||||||
|
previousSet.Difference(previousSetDiff)
|
||||||
|
}
|
||||||
|
(*b)[key] = previousSet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSet ...
|
||||||
|
func (b *UniqueBag) GetSet(id ID) BitSet { return (*b)[*id.ID] }
|
||||||
|
|
||||||
|
// List ...
|
||||||
|
func (b *UniqueBag) List() []ID {
|
||||||
|
idList := []ID(nil)
|
||||||
|
for id := range *b {
|
||||||
|
idList = append(idList, NewID(id))
|
||||||
|
}
|
||||||
|
return idList
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bag ...
|
||||||
|
func (b *UniqueBag) Bag(alpha int) Bag {
|
||||||
|
bag := Bag{}
|
||||||
|
bag.SetThreshold(alpha)
|
||||||
|
for id, bs := range *b {
|
||||||
|
bag.AddCount(NewID(id), bs.Len())
|
||||||
|
}
|
||||||
|
return bag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *UniqueBag) String() string {
|
||||||
|
sb := strings.Builder{}
|
||||||
|
|
||||||
|
sb.WriteString(fmt.Sprintf("UniqueBag: (Size = %d)", len(*b)))
|
||||||
|
for idBytes, set := range *b {
|
||||||
|
id := NewID(idBytes)
|
||||||
|
sb.WriteString(fmt.Sprintf("\n ID[%s]: Members = %s", id, set))
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package ids
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUniqueBag(t *testing.T) {
|
||||||
|
var ub1 UniqueBag
|
||||||
|
|
||||||
|
ub1.init()
|
||||||
|
|
||||||
|
if ub1 == nil {
|
||||||
|
t.Fatalf("Unique Bag still nil after initialized")
|
||||||
|
} else if len(ub1.List()) != 0 {
|
||||||
|
t.Fatalf("Unique Bag should be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
id1 := Empty.Prefix(1)
|
||||||
|
id2 := Empty.Prefix(2)
|
||||||
|
|
||||||
|
ub2 := make(UniqueBag)
|
||||||
|
ub2.Add(1, id1, id2)
|
||||||
|
|
||||||
|
if !ub2.GetSet(id1).Contains(1) {
|
||||||
|
t.Fatalf("Set missing element")
|
||||||
|
} else if !ub2.GetSet(id2).Contains(1) {
|
||||||
|
t.Fatalf("Set missing element")
|
||||||
|
}
|
||||||
|
|
||||||
|
var bs1 BitSet
|
||||||
|
bs1.Add(2)
|
||||||
|
bs1.Add(4)
|
||||||
|
|
||||||
|
ub3 := make(UniqueBag)
|
||||||
|
|
||||||
|
ub3.UnionSet(id1, bs1)
|
||||||
|
|
||||||
|
bs1.Clear()
|
||||||
|
bs1 = ub3.GetSet(id1)
|
||||||
|
if bs1.Len() != 2 {
|
||||||
|
t.Fatalf("Incorrect length of set")
|
||||||
|
} else if !bs1.Contains(2) {
|
||||||
|
t.Fatalf("Set missing element")
|
||||||
|
} else if !bs1.Contains(4) {
|
||||||
|
t.Fatalf("Set missing element")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Difference test
|
||||||
|
bs1.Clear()
|
||||||
|
|
||||||
|
ub4 := make(UniqueBag)
|
||||||
|
ub4.Add(1, id1)
|
||||||
|
ub4.Add(2, id1)
|
||||||
|
ub4.Add(5, id2)
|
||||||
|
ub4.Add(8, id2)
|
||||||
|
|
||||||
|
ub5 := make(UniqueBag)
|
||||||
|
ub5.Add(5, id2)
|
||||||
|
ub5.Add(5, id1)
|
||||||
|
|
||||||
|
ub4.Difference(&ub5)
|
||||||
|
|
||||||
|
if len(ub5.List()) != 2 {
|
||||||
|
t.Fatalf("Incorrect number of ids in Unique Bag")
|
||||||
|
}
|
||||||
|
|
||||||
|
ub4id1 := ub4.GetSet(id1)
|
||||||
|
if ub4id1.Len() != 2 {
|
||||||
|
t.Fatalf("Set of Unique Bag has incorrect length")
|
||||||
|
} else if !ub4id1.Contains(1) {
|
||||||
|
t.Fatalf("Set of Unique Bag missing element")
|
||||||
|
} else if !ub4id1.Contains(2) {
|
||||||
|
t.Fatalf("Set of Unique Bag missing element")
|
||||||
|
}
|
||||||
|
|
||||||
|
ub4id2 := ub4.GetSet(id2)
|
||||||
|
if ub4id2.Len() != 1 {
|
||||||
|
t.Fatalf("Set of Unique Bag has incorrect length")
|
||||||
|
} else if !ub4id2.Contains(8) {
|
||||||
|
t.Fatalf("Set of Unique Bag missing element")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DifferenceSet test
|
||||||
|
|
||||||
|
ub6 := make(UniqueBag)
|
||||||
|
ub6.Add(1, id1)
|
||||||
|
ub6.Add(2, id1)
|
||||||
|
ub6.Add(7, id1)
|
||||||
|
|
||||||
|
diffBitSet := BitSet(0)
|
||||||
|
diffBitSet.Add(1)
|
||||||
|
diffBitSet.Add(7)
|
||||||
|
|
||||||
|
ub6.DifferenceSet(id1, diffBitSet)
|
||||||
|
|
||||||
|
ub6id1 := ub6.GetSet(id1)
|
||||||
|
|
||||||
|
if ub6id1.Len() != 1 {
|
||||||
|
t.Fatalf("Set of Unique Bag missing element")
|
||||||
|
} else if !ub6id1.Contains(2) {
|
||||||
|
t.Fatalf("Set of Unique Bag missing element")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
openssl genrsa -out `dirname "$0"`/rootCA.key 4096
|
||||||
|
openssl req -x509 -new -nodes -key `dirname "$0"`/rootCA.key -sha256 -days 365250 -out `dirname "$0"`/rootCA.crt
|
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
openssl genrsa -out `dirname "$0"`/staker.key 4096
|
||||||
|
openssl req -new -sha256 -key `dirname "$0"`/staker.key -subj "/C=US/ST=NY/O=Avalabs/CN=ava" -out `dirname "$0"`/staker.csr
|
||||||
|
openssl x509 -req -in `dirname "$0"`/staker.csr -CA `dirname "$0"`/rootCA.crt -CAkey `dirname "$0"`/rootCA.key -CAcreateserial -out `dirname "$0"`/staker.crt -days 365250 -sha256
|
|
@ -0,0 +1,34 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
DB8IRfWqBx2nWw==
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,51 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1 @@
|
||||||
|
BAF3B5C5C6D0D14A
|
|
@ -0,0 +1,30 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIIEfzCCAmcCAQAwOjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5ZMRAwDgYDVQQK
|
||||||
|
DAdBdmFsYWJzMQwwCgYDVQQDDANhdmEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
|
||||||
|
ggIKAoICAQDKYSRw/W0YpYH/MTQhiFrR0m89l6yTuzLpDtjudr/5RnhIPvtqk7YI
|
||||||
|
Gm/m9l29xwR4J5r7SZGs+70yBetkbS+h7PwJ2rmWDwbrdyJKvVBhqf8kSn+VU2Le
|
||||||
|
PSIcJj193LDyWhV1H4lqNkUkcAR76Fh9qjMvA2p0vJ66+eDLXlph/RYapQx9HgOj
|
||||||
|
/0BmAKMrYCyo5BhRih+Ougg8aK4G9PQTIA5G2wTWW2QkHxM/QppFjZd/XwQeJ2H6
|
||||||
|
ubWMFc5fttf6AzpJvFIDBu/JDCKWiCu5m8t4GL8w2OrIx8Js19lF4YYE2eojCreq
|
||||||
|
gPi64S3ocqwKsDoySTw6/5iKQ5BUYwUXX3z7EXOqD8SMHefUKeczj4WvAaZLzR27
|
||||||
|
qXm55EgRYQAIX4fhmY7NfSop3Wh0Eo62+JHoM/1g+UgOXlbnWpY95Mgd7/fwDSWL
|
||||||
|
u4IxE0/uq8VufIbfC4yrY8qlTVfAffI1ldRdvJjPJBPiQ0CNrOl60LVptpkGc9sh
|
||||||
|
H7wZ2bP0bEnYKTgLAfOzD8Ut71O2AOIa80A1GNFl4Yle/MSNJOcQOSpgtWdREzIU
|
||||||
|
oenAjfuzM4OeTr4cRg4+VYTAo9KHKriN1DuewNzGd8WjKAVHmcIMjqISLTlzMhds
|
||||||
|
dm+OmfQ6OvyX7v0GTOBbhP09NGcww5A0gCzXN18FS5oxnxe6OG9D0wIDAQABoAAw
|
||||||
|
DQYJKoZIhvcNAQELBQADggIBAE7VplAZTEGHpYwXZvhlVg0qDsb/7IQj77eNteSU
|
||||||
|
33Dq6u7QLgS+Ea04Xv5BHnhnBoWRtrNR8WLTw64cuj6p/sqXiQsSNDgxNDPuPG+g
|
||||||
|
1FFi6wjgtoIJnx/QrITuUyO/MRy1awKLHlGfbY6yXSdLCC9bqLSIRm0tx+E+jo5C
|
||||||
|
0r5+ZOcLK8ZXWq9uHjmekX0hoN4qzsbQ0J5IeMh9ag+698aqzBSEDljLHg614yiK
|
||||||
|
FxtpD+23O0XfAdgqFgXRLLg3tt8AkVuys7r/uwHoz9du+nwW2U5nsMIYBXLV2mq3
|
||||||
|
1KbpXDTlVwaSoA2LP8dpmvbyTgNbXsjPdS91Rrzd7fcsammcSV0aWPiXmIbTLtn8
|
||||||
|
61ZRR0uj+jB68cRjSvegnheifsGyq6alr8OSUMdeWVyiPy2O7N6fUVj+Fmyzl5Ph
|
||||||
|
fl9UPZTmt/zOZrcSBoWjtZfmQVfw29SfMYwlNKALN4eOT6XwBLDK4uu4UXSoXwi+
|
||||||
|
V8evUUfBWcrcXHMTIFhoZbW/b7gjhnv148XWYI0ta8pjt/akzlPLtf4ETPqfECNN
|
||||||
|
4+p2w9+R5ktzCLeceXQc8eN+ZwjIt31zG48J7Sl1wJB13VR0jPy6zDsyUIswIVfe
|
||||||
|
7gp7GHg8R0lzDpEYCvU+R7RUWK6xcpjt7+mTHM70csnnOg7uPqnXqOdtVplr0y+R
|
||||||
|
pmqJ
|
||||||
|
-----END CERTIFICATE REQUEST-----
|
|
@ -0,0 +1,51 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
openssl genrsa -out `dirname "$0"`/rootCA.key 4096
|
||||||
|
openssl req -x509 -new -nodes -key `dirname "$0"`/rootCA.key -sha256 -days 365250 -out `dirname "$0"`/rootCA.crt
|
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
openssl genrsa -out `dirname "$0"`/staker.key 4096
|
||||||
|
openssl req -new -sha256 -key `dirname "$0"`/staker.key -subj "/C=US/ST=NY/O=Avalabs/CN=ava" -out `dirname "$0"`/staker.csr
|
||||||
|
openssl x509 -req -in `dirname "$0"`/staker.csr -CA `dirname "$0"`/rootCA.crt -CAkey `dirname "$0"`/rootCA.key -CAcreateserial -out `dirname "$0"`/staker.crt -days 365250 -sha256
|
|
@ -0,0 +1,34 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIF1jCCA76gAwIBAgIJALI1DF9cpwfEMA0GCSqGSIb3DQEBCwUAMH8xCzAJBgNV
|
||||||
|
BAYTAlVTMQswCQYDVQQIDAJOWTEPMA0GA1UEBwwGSXRoYWNhMRAwDgYDVQQKDAdB
|
||||||
|
dmFsYWJzMQ4wDAYDVQQLDAVHZWNrbzEMMAoGA1UEAwwDYXZhMSIwIAYJKoZIhvcN
|
||||||
|
AQkBFhNzdGVwaGVuQGF2YWxhYnMub3JnMCAXDTE5MDIyODIwNTkyNFoYDzMwMTkw
|
||||||
|
MzA4MjA1OTI0WjB/MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTlkxDzANBgNVBAcM
|
||||||
|
Bkl0aGFjYTEQMA4GA1UECgwHQXZhbGFiczEOMAwGA1UECwwFR2Vja28xDDAKBgNV
|
||||||
|
BAMMA2F2YTEiMCAGCSqGSIb3DQEJARYTc3RlcGhlbkBhdmFsYWJzLm9yZzCCAiIw
|
||||||
|
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJ45ScWV8tsCNO+NTIBuUYsPkhcg
|
||||||
|
jrp0HEyCHY3XEkxsLuDqtesNyv39YA0xQ3M3FP1e29tjFeHWJzyzV8O1H+6yco93
|
||||||
|
QAtzh9xELYD301Yq+x55yZrSjZxNIC5Tmz1ewTfD315lNR04M6JmqjrStIuLsWFU
|
||||||
|
m6P4OgXs4daqnyq9au4PYSrejcbexW59rKxLryK6Acv+E9Ax04oS33g9KqPmlRx0
|
||||||
|
lfu3x4nkIKIl+VaK1wC5CwJDYZ91KpEbC8Z2YvTeVDH+/hz/MvKl1CEaqK/4G5FB
|
||||||
|
KGEyd/bGRxMVQF41G7liJLaXzPLyZnKO2n21ZuJhkA9MZelt1U0LuQU505qU7IzW
|
||||||
|
cmKFEIb1MOrclaF19Is7HQlJWKyDo2/hfjSCZO8zH7eR9EGzKyQwZhwkYCycJD44
|
||||||
|
RKEHq6s/Z2dHUlpLIgRJ7k171TNkL9+xLntu8v1lzTkhemSNeO9asqJ7VcvpnMHH
|
||||||
|
bQXpDxJpi8jTnV8In8EolSqaKeN6/nzwbbSJ7gHehgpDhC1DlXPRzTt/ktQKlNGW
|
||||||
|
T5bdNdvYFyYTd9fu78aJZSbJo8jS2fykWuBgOgnlV8VmwpDa7iHM3EECByhf5GKB
|
||||||
|
J1jBlXO1ZyfJ7sNTbuVM7Uc2JkB4ASKdm3GZ3sFv95HjSTJAUORjE4pQ1es4kfDU
|
||||||
|
KqzDHH+bEHaGIGJTAgMBAAGjUzBRMB0GA1UdDgQWBBQr2T0duSMkvGXe3bSdWcei
|
||||||
|
73QtwzAfBgNVHSMEGDAWgBQr2T0duSMkvGXe3bSdWcei73QtwzAPBgNVHRMBAf8E
|
||||||
|
BTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQBpP18zCdzvnSdPigg9wx+a8Znr4aJj
|
||||||
|
FxZYwBY6/BmKb56ke9g+zKKCw2dYYkRYDcTOEfuBgBvNeCSJv4R5rmkukkL8RCIG
|
||||||
|
XV/WfSn2d3Mnz5KTgGQS6Q9s5qx+8ydkiGZthi+8a8ltXczyYrvWgd47U0NWTcOY
|
||||||
|
omjgF6XF+hVLWLgiwmA468pd7wyCsuJJkyxxeyDPXQ422I1AJW/7c5JQQa+lDNsv
|
||||||
|
Vna6420mZ/DiQd3stFkdjhRjmBZvGQ09g6l3zo6TgI1TWr5TMYPrempBVCWPNilC
|
||||||
|
XaMysU77+tPutI+7kMBuGvLuZtPrH/2uTYdXWPodyORm5i2ABF6In3VISPD9YNc0
|
||||||
|
gWx3PYGi2BfdnZepCojsffUMlhT3SsiAKMYv5FhW8LQBNMRR4721U1Vf5f8fzNQn
|
||||||
|
3E55TthV5HXZQ6HcLhkmOvH8CMqtWGETTbBtYSA2AVMjoqs7QDGkfsCH9UuwGd1N
|
||||||
|
W12JOf53XyOQT2XwWerSQC2kv7elsTh6Bk7PhvrCi0OwCVSGny5IQY/aXM1n6Z6s
|
||||||
|
scJlZmq6P3AJZ3tRtBt9yDK7iIW7mzNLTb/kAjsNQh06oETJIJ0CIgL0Bn6CANYU
|
||||||
|
kNqB4oTxmAhdOPKNgqaIwdZAL1VDIVaQEcvGeZRduo7yZvA/MhuQD8IIKSeOBFaD
|
||||||
|
DB8IRfWqBx2nWw==
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,51 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1 @@
|
||||||
|
BAF3B5C5C6D0D14A
|
|
@ -0,0 +1,30 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIIEfzCCAmcCAQAwOjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5ZMRAwDgYDVQQK
|
||||||
|
DAdBdmFsYWJzMQwwCgYDVQQDDANhdmEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
|
||||||
|
ggIKAoICAQDdToR60na6NuR9iSAUMyzPXJNMWVQbLyT5/iZCiJ3BB4YWMBhfxpJW
|
||||||
|
JiWXcM+znDgpJuyCEeh5Dp6ZY3Fe7k6Hht6FmFpDjwnjpQmdkEKUg00G+ElPTp/U
|
||||||
|
smsPL+JAswPqBZWpMBS3dsXQNunMMtMGlrf5S0l6XX4y7kc/GTxYgveWZ9JtR/m2
|
||||||
|
KNer+wjgBHqJ4rPqnHB30sDYPZg91Cz1Ak8Bb2w2I108zQVgKK6eIqNKXJJ/4piz
|
||||||
|
SZdU4920wMxYBpnfDAchnxei9U/v3QbT7eKUI2fGr+hOWTIWU80+VeOBt8a6P4sS
|
||||||
|
9AQh5/6G8qwmAqO3YQ9dxN82iu/H3+N+GGa/M0r5rEWrzwIuFhwKvyQcpPRBm2yQ
|
||||||
|
nBnhL9G5kN6n4OBM0KsgZ3CYlHZSg4eWcNgBt1WCFsQc7vfUFaJnr8QP3pF4V/4B
|
||||||
|
ok7wTO5HN0A1EYEVYuX53NGnrKVe+Fg9+xMOgXPWkUNqdvpI9ZbV3Z0S5866qF3/
|
||||||
|
vBZrhgCrKc5E/vMexBRe8Ki4wKqONVhi9WGUcRHvFEikc+7VrPj0YaG6zVLd+uOA
|
||||||
|
JN81fKOPYo4X4sZrMyPYl3OjGtMhfV4KvCaLEr1duOklqO6cCvGQ8iAlLVy3VJyW
|
||||||
|
5GJ0D0KyiAir4VNdAJKo1ZgiGivJLWulTfjUifCN9o115AiqJxiqwwIDAQABoAAw
|
||||||
|
DQYJKoZIhvcNAQELBQADggIBAM2IHKsQsebxTD50QQXtSNbyRzG/GpMZuZXn/QYO
|
||||||
|
QGW0ThJwtcmx6cqQvuyBovH5WhB9QUBFjiKkR7Qef7HUsgxU1cJA75gBfb2GMUru
|
||||||
|
Q+T37xOxtr6S2TcKOq/LvdJaTYmAHmW9V7vwEcrMRa9lWVTEmJIKTuxiUubpXtup
|
||||||
|
8OB8WLIvDikVtKtegvl6VCaTApCkUfuLhf7DERQ6sGLXWz6dVQcfvbfcXK2fn1Ik
|
||||||
|
Koxqy1SSz/rPb4u9NEk1yqvJQdpgnbTM3drTPHiIHCA7F6SjMu5tekHtVQkFOd6c
|
||||||
|
B0geEwyxY97zqnFv5YXiukXEaAnCRAlOuIZXRqtK6GFthTWo33YpB2KaRUtJ7IuP
|
||||||
|
og4Q/zjDs8DEc/qbbUbhyulExz6uoyRKO4j/gG3ESC6j09j7Eungt1LDhyt8p3wD
|
||||||
|
pytIIPkTseykO0CcEpEcGbES6d3u4PrFJ75XWxMkNZVK8mC3faxx2kJLfS1+4Fg8
|
||||||
|
A0zbcN6qwm1ezGq2vGQcyVKyFVWJQAEAFuSO8sjW6dk3ClfE+MNGUvxTQMe96V14
|
||||||
|
jGRICCp9aJrJXA3u0iQaUX0cXmlhegAYk7Ho/Ef3k/PcP8DzZ8Ck839oRHBw4pPv
|
||||||
|
tKbyiKnOcet7AFGwsiM2t5VLrj4jovhRLEiaXrCaxNe6j4xs63TEb+8uTCzKyktC
|
||||||
|
4BFq
|
||||||
|
-----END CERTIFICATE REQUEST-----
|
|
@ -0,0 +1,51 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
openssl genrsa -out `dirname "$0"`/rootCA.key 4096
|
||||||
|
openssl req -x509 -new -nodes -key `dirname "$0"`/rootCA.key -sha256 -days 365250 -out `dirname "$0"`/rootCA.crt
|
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
openssl genrsa -out `dirname "$0"`/staker.key 4096
|
||||||
|
openssl req -new -sha256 -key `dirname "$0"`/staker.key -subj "/C=US/ST=NY/O=Avalabs/CN=ava" -out `dirname "$0"`/staker.csr
|
||||||
|
openssl x509 -req -in `dirname "$0"`/staker.csr -CA `dirname "$0"`/rootCA.crt -CAkey `dirname "$0"`/rootCA.key -CAcreateserial -out `dirname "$0"`/staker.crt -days 365250 -sha256
|
|
@ -0,0 +1,34 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
DB8IRfWqBx2nWw==
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,51 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1 @@
|
||||||
|
BAF3B5C5C6D0D14A
|
|
@ -0,0 +1,30 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIIEfzCCAmcCAQAwOjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5ZMRAwDgYDVQQK
|
||||||
|
DAdBdmFsYWJzMQwwCgYDVQQDDANhdmEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
|
||||||
|
ggIKAoICAQC8mVDToHbkUF2gRdVfpydZLNKeQ38d6HZFkUM3U1dWLZFSZNvagN8h
|
||||||
|
lQvY/tQu3A40p19WgKbzWZre3tg1Akw8Jztdz9gl4RMn142IIO3CiwIptkE0Jopb
|
||||||
|
ZhmG5fAC2n/MXQtfieI3hzeR04LW4JgLKzf3Nn8xZdlBgJfBmL5qUUnE7O7IbJGG
|
||||||
|
ma6gSD3ewetE6KQZtNtf0xRIv08doZKYwTl6ItkdGK76ufqq098GVwWvA1wSune4
|
||||||
|
+MFgs9N4eFJj6Jyt85fiK/cwPx7KRdgYgBzrZQ4EPshRnwWrBTieOOaJvAA2RMxM
|
||||||
|
EYzKRrJAAsYI1zxtNyqIUaBTcxmaz+NXUGW+wHwITic0Gp/XQm2Lwr/lxIV6OnAl
|
||||||
|
L3CgbSXirSnoG+eHQ+vDzBAcRDkTAgv/GUIzlfqT2StTK02uIBgJYzvFTG4plHit
|
||||||
|
ccRfy8wxsh5Z8xG99lmPQQtLsnlQAV+Li06Cb8CH4hUVoiWiVs5QAahqWmv5fpoX
|
||||||
|
0Es26RyUHXGbjE202pyMMA7jUerUVKMijOoGZtcH6zB4p/dJ0TtToRwOgrA7NCI9
|
||||||
|
AYVtqVXrXG/udj8ur2r1bTVwIbHsOeTEP3gY0mHRWm2E/bLjt9vbYIRUxR8xWnLk
|
||||||
|
beBziNTwg+36jdDF+6gu3cUz/nbSn8YY+Y1jjXuM3lqF8iMaAobhuwIDAQABoAAw
|
||||||
|
DQYJKoZIhvcNAQELBQADggIBAEWU13T1alCni4R36J65TrGfIljW8LGhmWRo5xoV
|
||||||
|
YW7HxxZ/WTAFSwAv0yHCGq+H/tebRZhvua+c+jP16YBDoAp5neGWW57gLDg+35H5
|
||||||
|
guLo73p/qM6hyaUGSfyO9D1nS1QX8R0r70TQYbIrVB4uQeTM2pEYR6NYO7bjPEWr
|
||||||
|
WwC6RnbtnsNGTeoH+LwiM+uY//VB/tUe1u2y6U8HkIXJo7j4+NqUL1xXmYmC6Rph
|
||||||
|
PNI3MAZUL40z1VX7fn/Vp7+rc0CBUsFMOLfLmSgL8jsQeKuyVAQKA4xzWQ2qeuGV
|
||||||
|
Bv24rHbnSxYSu8tMs31LZPn+fsvNWB9iU7MEiTUr+8nAPEAANNaBwaD1EUkzC1WC
|
||||||
|
OcCUpMgkhVuzfHq+eXWnw3cGVvEZ8A4DzOyl1ZFofxBX7IOOv0lmpDQSeEQlmKPF
|
||||||
|
LdWI2JJM76BjeXI7l5HbOmRJv1kYFwBq/biDxCSmmNX8uHvAj1EgDNXvr/pRw7rT
|
||||||
|
6yKOLtk1GSCCrrYQijCXRx2u276+j8MtC5i6FVcUoaSYD+nx2+ApOnZlYR7xsJYw
|
||||||
|
5ECaeDagjHP472FY/fuhy/UwAIFm5gCcVFs3A2M/Iyn+vsAW5WEdh+fwGiWxfw49
|
||||||
|
Y5KRT8u7BD0R5T5UYxYwzYekEzxsoD3bvQGx/4tboSUxkOd7pVymbuGzIsQ18heI
|
||||||
|
78pG
|
||||||
|
-----END CERTIFICATE REQUEST-----
|
|
@ -0,0 +1,51 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
openssl genrsa -out `dirname "$0"`/rootCA.key 4096
|
||||||
|
openssl req -x509 -new -nodes -key `dirname "$0"`/rootCA.key -sha256 -days 365250 -out `dirname "$0"`/rootCA.crt
|
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
openssl genrsa -out `dirname "$0"`/staker.key 4096
|
||||||
|
openssl req -new -sha256 -key `dirname "$0"`/staker.key -subj "/C=US/ST=NY/O=Avalabs/CN=ava" -out `dirname "$0"`/staker.csr
|
||||||
|
openssl x509 -req -in `dirname "$0"`/staker.csr -CA `dirname "$0"`/rootCA.crt -CAkey `dirname "$0"`/rootCA.key -CAcreateserial -out `dirname "$0"`/staker.crt -days 365250 -sha256
|
|
@ -0,0 +1,34 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
DB8IRfWqBx2nWw==
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,51 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1 @@
|
||||||
|
BAF3B5C5C6D0D14A
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue