Files
2023-08-24 15:28:17 -04:00

988 lines
29 KiB
Go

// Copyright (C) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package integration_test
import (
"context"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"net/http/httptest"
"os"
"testing"
"time"
"github.com/ava-labs/avalanchego/api/metrics"
"github.com/ava-labs/avalanchego/database/manager"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/snow"
"github.com/ava-labs/avalanchego/snow/choices"
"github.com/ava-labs/avalanchego/snow/consensus/snowman"
"github.com/ava-labs/avalanchego/snow/engine/common"
"github.com/ava-labs/avalanchego/snow/validators"
"github.com/ava-labs/avalanchego/utils/crypto/bls"
"github.com/ava-labs/avalanchego/utils/logging"
"github.com/ava-labs/avalanchego/utils/set"
avago_version "github.com/ava-labs/avalanchego/version"
"github.com/ava-labs/avalanchego/vms/platformvm/warp"
"github.com/fatih/color"
ginkgo "github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
"go.uber.org/zap"
"github.com/ava-labs/hypersdk/chain"
"github.com/ava-labs/hypersdk/codec"
"github.com/ava-labs/hypersdk/consts"
"github.com/ava-labs/hypersdk/crypto"
"github.com/ava-labs/hypersdk/pubsub"
"github.com/ava-labs/hypersdk/rpc"
hutils "github.com/ava-labs/hypersdk/utils"
"github.com/ava-labs/hypersdk/vm"
"github.com/bianyuanop/oraclevm/actions"
"github.com/bianyuanop/oraclevm/auth"
lconsts "github.com/bianyuanop/oraclevm/consts"
"github.com/bianyuanop/oraclevm/controller"
"github.com/bianyuanop/oraclevm/genesis"
"github.com/bianyuanop/oraclevm/oracle"
lrpc "github.com/bianyuanop/oraclevm/rpc"
"github.com/bianyuanop/oraclevm/utils"
)
const transferTxFee = 400 /* base fee */ + 40 /* transfer fee */
var (
logFactory logging.Factory
log logging.Logger
)
func init() {
logFactory = logging.NewFactory(logging.Config{
DisplayLevel: logging.Debug,
})
l, err := logFactory.Make("main")
if err != nil {
panic(err)
}
log = l
}
func TestIntegration(t *testing.T) {
gomega.RegisterFailHandler(ginkgo.Fail)
ginkgo.RunSpecs(t, "morpheusvm integration test suites")
}
var (
requestTimeout time.Duration
vms int
minPrice int64
)
func init() {
flag.DurationVar(
&requestTimeout,
"request-timeout",
120*time.Second,
"timeout for transaction issuance and confirmation",
)
flag.IntVar(
&vms,
"vms",
3,
"number of VMs to create",
)
flag.Int64Var(
&minPrice,
"min-price",
-1,
"minimum price",
)
}
var (
priv crypto.PrivateKey
factory *auth.ED25519Factory
rsender crypto.PublicKey
sender string
priv2 crypto.PrivateKey
factory2 *auth.ED25519Factory
rsender2 crypto.PublicKey
sender2 string
priv3 crypto.PrivateKey
factory3 *auth.ED25519Factory
rsender3 crypto.PublicKey
sender3 string
// when used with embedded VMs
genesisBytes []byte
instances []instance
networkID uint32
gen *genesis.Genesis
)
type instance struct {
chainID ids.ID
nodeID ids.NodeID
vm *vm.VM
toEngine chan common.Message
JSONRPCServer *httptest.Server
BaseJSONRPCServer *httptest.Server
WebSocketServer *httptest.Server
cli *rpc.JSONRPCClient // clients for embedded VMs
lcli *lrpc.JSONRPCClient
}
var _ = ginkgo.BeforeSuite(func() {
log.Info("VMID", zap.Stringer("id", lconsts.ID))
gomega.Ω(vms).Should(gomega.BeNumerically(">", 1))
var err error
priv, err = crypto.GeneratePrivateKey()
gomega.Ω(err).Should(gomega.BeNil())
factory = auth.NewED25519Factory(priv)
rsender = priv.PublicKey()
sender = utils.Address(rsender)
log.Debug(
"generated key",
zap.String("addr", sender),
zap.String("pk", hex.EncodeToString(priv[:])),
)
priv2, err = crypto.GeneratePrivateKey()
gomega.Ω(err).Should(gomega.BeNil())
factory2 = auth.NewED25519Factory(priv2)
rsender2 = priv2.PublicKey()
sender2 = utils.Address(rsender2)
log.Debug(
"generated key",
zap.String("addr", sender2),
zap.String("pk", hex.EncodeToString(priv2[:])),
)
priv3, err = crypto.GeneratePrivateKey()
gomega.Ω(err).Should(gomega.BeNil())
factory3 = auth.NewED25519Factory(priv3)
rsender3 = priv3.PublicKey()
sender3 = utils.Address(rsender3)
log.Debug(
"generated key",
zap.String("addr", sender3),
zap.String("pk", hex.EncodeToString(priv3[:])),
)
// create embedded VMs
instances = make([]instance, vms)
gen = genesis.Default()
if minPrice >= 0 {
gen.MinUnitPrice = uint64(minPrice)
}
gen.MinBlockGap = 0
gen.CustomAllocation = []*genesis.CustomAllocation{
{
Address: sender,
Balance: 10_000_000,
},
}
genesisBytes, err = json.Marshal(gen)
gomega.Ω(err).Should(gomega.BeNil())
networkID = uint32(1)
subnetID := ids.GenerateTestID()
chainID := ids.GenerateTestID()
app := &appSender{}
for i := range instances {
nodeID := ids.GenerateTestNodeID()
sk, err := bls.NewSecretKey()
gomega.Ω(err).Should(gomega.BeNil())
l, err := logFactory.Make(nodeID.String())
gomega.Ω(err).Should(gomega.BeNil())
dname, err := os.MkdirTemp("", fmt.Sprintf("%s-chainData", nodeID.String()))
gomega.Ω(err).Should(gomega.BeNil())
snowCtx := &snow.Context{
NetworkID: networkID,
SubnetID: subnetID,
ChainID: chainID,
NodeID: nodeID,
Log: l,
ChainDataDir: dname,
Metrics: metrics.NewOptionalGatherer(),
PublicKey: bls.PublicFromSecretKey(sk),
WarpSigner: warp.NewSigner(sk, networkID, chainID),
ValidatorState: &validators.TestState{},
}
toEngine := make(chan common.Message, 1)
db := manager.NewMemDB(avago_version.CurrentDatabase)
v := controller.New()
err = v.Initialize(
context.TODO(),
snowCtx,
db,
genesisBytes,
nil,
[]byte(
`{"parallelism":3, "testMode":true, "logLevel":"debug", "trackedStocks": ["AMD", "Apple"]}`,
),
toEngine,
nil,
app,
)
gomega.Ω(err).Should(gomega.BeNil())
var hd map[string]*common.HTTPHandler
hd, err = v.CreateHandlers(context.TODO())
gomega.Ω(err).Should(gomega.BeNil())
jsonRPCServer := httptest.NewServer(hd[rpc.JSONRPCEndpoint].Handler)
ljsonRPCServer := httptest.NewServer(hd[lrpc.JSONRPCEndpoint].Handler)
webSocketServer := httptest.NewServer(hd[rpc.WebSocketEndpoint].Handler)
instances[i] = instance{
chainID: snowCtx.ChainID,
nodeID: snowCtx.NodeID,
vm: v,
toEngine: toEngine,
JSONRPCServer: jsonRPCServer,
BaseJSONRPCServer: ljsonRPCServer,
WebSocketServer: webSocketServer,
cli: rpc.NewJSONRPCClient(jsonRPCServer.URL),
lcli: lrpc.NewJSONRPCClient(ljsonRPCServer.URL, snowCtx.NetworkID, snowCtx.ChainID),
}
// Force sync ready (to mimic bootstrapping from genesis)
v.ForceReady()
}
// Verify genesis allocations loaded correctly (do here otherwise test may
// check during and it will be inaccurate)
for _, inst := range instances {
cli := inst.lcli
g, err := cli.Genesis(context.Background())
gomega.Ω(err).Should(gomega.BeNil())
csupply := uint64(0)
for _, alloc := range g.CustomAllocation {
balance, err := cli.Balance(context.Background(), alloc.Address)
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(balance).Should(gomega.Equal(alloc.Balance))
csupply += alloc.Balance
}
}
app.instances = instances
color.Blue("created %d VMs", vms)
})
var _ = ginkgo.AfterSuite(func() {
for _, iv := range instances {
iv.JSONRPCServer.Close()
iv.BaseJSONRPCServer.Close()
iv.WebSocketServer.Close()
err := iv.vm.Shutdown(context.TODO())
gomega.Ω(err).Should(gomega.BeNil())
}
})
var _ = ginkgo.Describe("[Ping]", func() {
ginkgo.It("can ping", func() {
for _, inst := range instances {
cli := inst.cli
ok, err := cli.Ping(context.Background())
gomega.Ω(ok).Should(gomega.BeTrue())
gomega.Ω(err).Should(gomega.BeNil())
}
})
})
var _ = ginkgo.Describe("[Network]", func() {
ginkgo.It("can get network", func() {
for _, inst := range instances {
cli := inst.cli
networkID, subnetID, chainID, err := cli.Network(context.Background())
gomega.Ω(networkID).Should(gomega.Equal(uint32(1)))
gomega.Ω(subnetID).ShouldNot(gomega.Equal(ids.Empty))
gomega.Ω(chainID).ShouldNot(gomega.Equal(ids.Empty))
gomega.Ω(err).Should(gomega.BeNil())
}
})
})
var _ = ginkgo.Describe("[Tx Processing]", func() {
ginkgo.It("get currently accepted block ID", func() {
for _, inst := range instances {
cli := inst.cli
_, _, _, err := cli.Accepted(context.Background())
gomega.Ω(err).Should(gomega.BeNil())
}
})
var transferTxRoot *chain.Transaction
ginkgo.It("Gossip TransferTx to a different node", func() {
ginkgo.By("issue TransferTx", func() {
parser, err := instances[0].lcli.Parser(context.Background())
gomega.Ω(err).Should(gomega.BeNil())
submit, transferTx, _, err := instances[0].cli.GenerateTransaction(
context.Background(),
parser,
nil,
&actions.Transfer{
To: rsender2,
Value: 100_000, // must be more than StateLockup
},
factory,
)
transferTxRoot = transferTx
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(submit(context.Background())).Should(gomega.BeNil())
gomega.Ω(instances[0].vm.Mempool().Len(context.Background())).Should(gomega.Equal(1))
})
ginkgo.By("skip duplicate", func() {
_, err := instances[0].cli.SubmitTx(
context.Background(),
transferTxRoot.Bytes(),
)
gomega.Ω(err).To(gomega.Not(gomega.BeNil()))
})
ginkgo.By("send gossip from node 0 to 1", func() {
err := instances[0].vm.Gossiper().ForceGossip(context.TODO())
gomega.Ω(err).Should(gomega.BeNil())
})
ginkgo.By("skip invalid time", func() {
tx := chain.NewTx(
&chain.Base{
ChainID: instances[0].chainID,
Timestamp: 0,
UnitPrice: 1000,
},
nil,
&actions.Transfer{
To: rsender2,
Value: 110,
},
)
// Must do manual construction to avoid `tx.Sign` error (would fail with
// 0 timestamp)
msg, err := tx.Digest()
gomega.Ω(err).To(gomega.BeNil())
auth, err := factory.Sign(msg, tx.Action)
gomega.Ω(err).To(gomega.BeNil())
tx.Auth = auth
p := codec.NewWriter(0, consts.MaxInt) // test codec growth
gomega.Ω(tx.Marshal(p)).To(gomega.BeNil())
gomega.Ω(p.Err()).To(gomega.BeNil())
_, err = instances[0].cli.SubmitTx(
context.Background(),
p.Bytes(),
)
gomega.Ω(err).To(gomega.Not(gomega.BeNil()))
})
ginkgo.By("skip duplicate (after gossip, which shouldn't clear)", func() {
_, err := instances[0].cli.SubmitTx(
context.Background(),
transferTxRoot.Bytes(),
)
gomega.Ω(err).To(gomega.Not(gomega.BeNil()))
})
ginkgo.By("receive gossip in the node 1, and signal block build", func() {
instances[1].vm.Builder().ForceNotify()
<-instances[1].toEngine
})
ginkgo.By("build block in the node 1", func() {
ctx := context.TODO()
blk, err := instances[1].vm.BuildBlock(ctx)
gomega.Ω(err).To(gomega.BeNil())
gomega.Ω(blk.Verify(ctx)).To(gomega.BeNil())
gomega.Ω(blk.Status()).To(gomega.Equal(choices.Processing))
err = instances[1].vm.SetPreference(ctx, blk.ID())
gomega.Ω(err).To(gomega.BeNil())
gomega.Ω(blk.Accept(ctx)).To(gomega.BeNil())
gomega.Ω(blk.Status()).To(gomega.Equal(choices.Accepted))
lastAccepted, err := instances[1].vm.LastAccepted(ctx)
gomega.Ω(err).To(gomega.BeNil())
gomega.Ω(lastAccepted).To(gomega.Equal(blk.ID()))
results := blk.(*chain.StatelessBlock).Results()
gomega.Ω(results).Should(gomega.HaveLen(1))
gomega.Ω(results[0].Success).Should(gomega.BeTrue())
gomega.Ω(results[0].Units).Should(gomega.Equal(uint64(transferTxFee)))
gomega.Ω(results[0].Output).Should(gomega.BeNil())
})
ginkgo.By("ensure balance is updated", func() {
balance, err := instances[1].lcli.Balance(context.Background(), sender)
gomega.Ω(err).To(gomega.BeNil())
gomega.Ω(balance).To(gomega.Equal(uint64(9899560)))
balance2, err := instances[1].lcli.Balance(context.Background(), sender2)
gomega.Ω(err).To(gomega.BeNil())
gomega.Ω(balance2).To(gomega.Equal(uint64(100000)))
})
})
ginkgo.It("ensure multiple txs work ", func() {
ginkgo.By("transfer funds again", func() {
parser, err := instances[1].lcli.Parser(context.Background())
gomega.Ω(err).Should(gomega.BeNil())
submit, _, _, err := instances[1].cli.GenerateTransaction(
context.Background(),
parser,
nil,
&actions.Transfer{
To: rsender2,
Value: 101,
},
factory,
)
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(submit(context.Background())).Should(gomega.BeNil())
accept := expectBlk(instances[1])
results := accept()
gomega.Ω(results).Should(gomega.HaveLen(1))
gomega.Ω(results[0].Success).Should(gomega.BeTrue())
balance2, err := instances[1].lcli.Balance(context.Background(), sender2)
gomega.Ω(err).To(gomega.BeNil())
gomega.Ω(balance2).To(gomega.Equal(uint64(100101)))
})
})
ginkgo.It("Test processing block handling", func() {
var accept, accept2 func() []*chain.Result
ginkgo.By("create processing tip", func() {
parser, err := instances[1].lcli.Parser(context.Background())
gomega.Ω(err).Should(gomega.BeNil())
submit, _, _, err := instances[1].cli.GenerateTransaction(
context.Background(),
parser,
nil,
&actions.Transfer{
To: rsender2,
Value: 200,
},
factory,
)
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(submit(context.Background())).Should(gomega.BeNil())
accept = expectBlk(instances[1])
submit, _, _, err = instances[1].cli.GenerateTransaction(
context.Background(),
parser,
nil,
&actions.Transfer{
To: rsender2,
Value: 201,
},
factory,
)
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(submit(context.Background())).Should(gomega.BeNil())
accept2 = expectBlk(instances[1])
})
ginkgo.By("clear processing tip", func() {
results := accept()
gomega.Ω(results).Should(gomega.HaveLen(1))
gomega.Ω(results[0].Success).Should(gomega.BeTrue())
results = accept2()
gomega.Ω(results).Should(gomega.HaveLen(1))
gomega.Ω(results[0].Success).Should(gomega.BeTrue())
})
})
ginkgo.It("ensure mempool works", func() {
ginkgo.By("fail Gossip TransferTx to a stale node when missing previous blocks", func() {
parser, err := instances[1].lcli.Parser(context.Background())
gomega.Ω(err).Should(gomega.BeNil())
submit, _, _, err := instances[1].cli.GenerateTransaction(
context.Background(),
parser,
nil,
&actions.Transfer{
To: rsender2,
Value: 203,
},
factory,
)
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(submit(context.Background())).Should(gomega.BeNil())
err = instances[1].vm.Gossiper().ForceGossip(context.TODO())
gomega.Ω(err).Should(gomega.BeNil())
// mempool in 0 should be 1 (old amount), since gossip/submit failed
gomega.Ω(instances[0].vm.Mempool().Len(context.TODO())).Should(gomega.Equal(1))
})
})
ginkgo.It("ensure unprocessed tip works", func() {
ginkgo.By("import accepted blocks to instance 2", func() {
ctx := context.TODO()
o := instances[1]
blks := []snowman.Block{}
next, err := o.vm.LastAccepted(ctx)
gomega.Ω(err).Should(gomega.BeNil())
for {
blk, err := o.vm.GetBlock(ctx, next)
gomega.Ω(err).Should(gomega.BeNil())
blks = append([]snowman.Block{blk}, blks...)
if blk.Height() == 1 {
break
}
next = blk.Parent()
}
n := instances[2]
blk1, err := n.vm.ParseBlock(ctx, blks[0].Bytes())
gomega.Ω(err).Should(gomega.BeNil())
err = blk1.Verify(ctx)
gomega.Ω(err).Should(gomega.BeNil())
// Parse tip
blk2, err := n.vm.ParseBlock(ctx, blks[1].Bytes())
gomega.Ω(err).Should(gomega.BeNil())
blk3, err := n.vm.ParseBlock(ctx, blks[2].Bytes())
gomega.Ω(err).Should(gomega.BeNil())
// Verify tip
err = blk2.Verify(ctx)
gomega.Ω(err).Should(gomega.BeNil())
err = blk3.Verify(ctx)
gomega.Ω(err).Should(gomega.BeNil())
// Accept tip
err = blk1.Accept(ctx)
gomega.Ω(err).Should(gomega.BeNil())
err = blk2.Accept(ctx)
gomega.Ω(err).Should(gomega.BeNil())
err = blk3.Accept(ctx)
gomega.Ω(err).Should(gomega.BeNil())
// Parse another
blk4, err := n.vm.ParseBlock(ctx, blks[3].Bytes())
gomega.Ω(err).Should(gomega.BeNil())
err = blk4.Verify(ctx)
gomega.Ω(err).Should(gomega.BeNil())
err = blk4.Accept(ctx)
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(n.vm.SetPreference(ctx, blk4.ID())).Should(gomega.BeNil())
})
})
ginkgo.It("processes valid index transactions (w/block listening)", func() {
// Clear previous txs on instance 0
accept := expectBlk(instances[0])
accept() // don't care about results
// Subscribe to blocks
cli, err := rpc.NewWebSocketClient(instances[0].WebSocketServer.URL, rpc.DefaultHandshakeTimeout, pubsub.MaxPendingMessages, pubsub.MaxReadMessageSize)
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(cli.RegisterBlocks()).Should(gomega.BeNil())
// Wait for message to be sent
time.Sleep(2 * pubsub.MaxMessageWait)
// Fetch balances
balance, err := instances[0].lcli.Balance(context.TODO(), sender)
gomega.Ω(err).Should(gomega.BeNil())
// Send tx
other, err := crypto.GeneratePrivateKey()
gomega.Ω(err).Should(gomega.BeNil())
transfer := &actions.Transfer{
To: other.PublicKey(),
Value: 1,
}
parser, err := instances[0].lcli.Parser(context.Background())
gomega.Ω(err).Should(gomega.BeNil())
submit, rawTx, _, err := instances[0].cli.GenerateTransaction(
context.Background(),
parser,
nil,
transfer,
factory,
)
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(submit(context.Background())).Should(gomega.BeNil())
gomega.Ω(err).Should(gomega.BeNil())
accept = expectBlk(instances[0])
results := accept()
gomega.Ω(results).Should(gomega.HaveLen(1))
gomega.Ω(results[0].Success).Should(gomega.BeTrue())
// Read item from connection
blk, lresults, err := cli.ListenBlock(context.TODO(), parser)
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(len(blk.Txs)).Should(gomega.Equal(1))
tx := blk.Txs[0].Action.(*actions.Transfer)
gomega.Ω(tx.Value).To(gomega.Equal(uint64(1)))
gomega.Ω(lresults).Should(gomega.Equal(results))
// Check balance modifications are correct
balancea, err := instances[0].lcli.Balance(context.TODO(), sender)
gomega.Ω(err).Should(gomega.BeNil())
g, err := instances[0].lcli.Genesis(context.TODO())
gomega.Ω(err).Should(gomega.BeNil())
r := g.Rules(time.Now().UnixMilli(), networkID, instances[0].chainID)
maxUnits, err := rawTx.MaxUnits(r)
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(balance).Should(gomega.Equal(balancea + maxUnits + 1))
// Close connection when done
gomega.Ω(cli.Close()).Should(gomega.BeNil())
})
ginkgo.It("processes valid index transactions (w/streaming verification)", func() {
// Create streaming client
cli, err := rpc.NewWebSocketClient(instances[0].WebSocketServer.URL, rpc.DefaultHandshakeTimeout, pubsub.MaxPendingMessages, pubsub.MaxReadMessageSize)
gomega.Ω(err).Should(gomega.BeNil())
// Create tx
other, err := crypto.GeneratePrivateKey()
gomega.Ω(err).Should(gomega.BeNil())
transfer := &actions.Transfer{
To: other.PublicKey(),
Value: 1,
}
parser, err := instances[0].lcli.Parser(context.Background())
gomega.Ω(err).Should(gomega.BeNil())
_, tx, _, err := instances[0].cli.GenerateTransaction(
context.Background(),
parser,
nil,
transfer,
factory,
)
gomega.Ω(err).Should(gomega.BeNil())
// Submit tx and accept block
gomega.Ω(cli.RegisterTx(tx)).Should(gomega.BeNil())
// Wait for message to be sent
time.Sleep(2 * pubsub.MaxMessageWait)
for instances[0].vm.Mempool().Len(context.TODO()) == 0 {
// We need to wait for mempool to be populated because issuance will
// return as soon as bytes are on the channel.
hutils.Outf("{{yellow}}waiting for mempool to return non-zero txs{{/}}\n")
time.Sleep(500 * time.Millisecond)
}
gomega.Ω(err).Should(gomega.BeNil())
accept := expectBlk(instances[0])
results := accept()
gomega.Ω(results).Should(gomega.HaveLen(1))
gomega.Ω(results[0].Success).Should(gomega.BeTrue())
// Read decision from connection
txID, dErr, result, err := cli.ListenTx(context.TODO())
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(txID).Should(gomega.Equal(tx.ID()))
gomega.Ω(dErr).Should(gomega.BeNil())
gomega.Ω(result.Success).Should(gomega.BeTrue())
gomega.Ω(result).Should(gomega.Equal(results[0]))
// Close connection when done
gomega.Ω(cli.Close()).Should(gomega.BeNil())
})
// ginkgo.It("test rpc methods work", func() {
// history, err := instances[0].lcli.AggregationHistory(context.TODO(), 0, 20)
// gomega.Ω(err).Should(gomega.BeNil())
// gomega.Ω(history).Should(gomega.HaveLen(0))
// })
ginkgo.It("ensure oracle configuration is properly loaded", func() {
metas, err := instances[0].lcli.AvailableEntities(context.TODO())
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(metas).Should(gomega.HaveLen(2))
amd := &oracle.EntityCollectionMeta{
EntityName: "AMD",
EntityID: 0,
EntityType: oracle.StockID,
}
apple := &oracle.EntityCollectionMeta{
EntityName: "Apple",
EntityID: 1,
EntityType: oracle.StockID,
}
gomega.Ω(metas[0].EntityID).Should(gomega.Equal(amd.EntityID))
gomega.Ω(metas[0].EntityName).Should(gomega.Equal(amd.EntityName))
gomega.Ω(metas[0].EntityType).Should(gomega.Equal(amd.EntityType))
gomega.Ω(metas[1].EntityID).Should(gomega.Equal(apple.EntityID))
gomega.Ω(metas[1].EntityName).Should(gomega.Equal(apple.EntityName))
gomega.Ω(metas[1].EntityType).Should(gomega.Equal(apple.EntityType))
})
ginkgo.It("testing functionality of entity execution", func() {
parser, err := instances[0].lcli.Parser(context.Background())
gomega.Ω(err).Should(gomega.BeNil())
submit, _, _, err := instances[0].cli.GenerateTransaction(
context.Background(),
parser,
nil,
&actions.UploadEntity{
EntityIndex: 0,
EntityType: 0,
Payload: []byte(`{ "ticker": "AMD", "price": 1999 }`),
},
factory,
)
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(submit(context.Background())).Should(gomega.BeNil())
accept := expectBlk(instances[0])
results := accept()
gomega.Ω(results).Should(gomega.HaveLen(1))
output := results[0].Output
entityWithMeta, err := oracle.UnmarshalEntityWithMeta(output)
gomega.Ω(err).Should(gomega.BeNil())
s, ok := entityWithMeta.Entity.(*oracle.Stock)
gomega.Ω(ok).Should(gomega.BeTrue())
gomega.Ω(s.Price).Should(gomega.Equal(uint64(1999)))
gomega.Ω(s.Ticker).Should(gomega.Equal("AMD"))
time.Sleep(2 * time.Second)
count, err := instances[0].lcli.CollectionCount(context.TODO(), 0)
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(count).Should(gomega.Equal(uint64(1)))
history, length, err := instances[0].lcli.AggregationHistory(context.TODO(), 0, 1)
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(length).Should(gomega.Equal(1))
stock, err := oracle.UnmarshalStock(history[0])
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(stock.Price).Should(gomega.Equal(uint64(1999)))
gomega.Ω(stock.Ticker).Should(gomega.Equal("AMD"))
})
ginkgo.It("test functionality of stock aggregation", func() {
ginkgo.By("ensure there is already one aggregation result in there", func() {
historyLen, err := instances[0].lcli.CollectionCount(context.TODO(), 0)
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(historyLen).Should(gomega.Equal(uint64(1)))
})
ginkgo.By("submitting two transactions", func() {
parser, err := instances[0].lcli.Parser(context.Background())
gomega.Ω(err).Should(gomega.BeNil())
submit, _, _, err := instances[0].cli.GenerateTransaction(
context.Background(),
parser,
nil,
&actions.UploadEntity{
EntityIndex: 0,
EntityType: 0,
Payload: []byte(`{ "ticker": "AMD", "price": 20 }`),
},
factory,
)
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(submit(context.Background())).Should(gomega.BeNil())
submit2, _, _, err2 := instances[0].cli.GenerateTransaction(
context.Background(),
parser,
nil,
&actions.UploadEntity{
EntityIndex: 0,
EntityType: 0,
Payload: []byte(`{ "ticker": "AMD", "price": 30 }`),
},
factory,
)
gomega.Ω(err2).Should(gomega.BeNil())
gomega.Ω(submit2(context.Background())).Should(gomega.BeNil())
})
ginkgo.By("build block and check aggregation result", func() {
accept := expectBlk(instances[0])
results := accept()
gomega.Ω(results).Should(gomega.HaveLen(2))
time.Sleep(2 * time.Second)
historyLen, err := instances[0].lcli.CollectionCount(context.TODO(), 0)
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(historyLen).Should(gomega.Equal(uint64(2)))
history, length, err := instances[0].lcli.AggregationHistory(context.TODO(), 0, 1)
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(length).Should(gomega.Equal(1))
stock, err := oracle.UnmarshalStock(history[0])
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(stock.Price).Should(gomega.Equal(uint64(25)))
gomega.Ω(stock.Ticker).Should(gomega.Equal("AMD"))
})
})
ginkgo.It("test warp query", func() {
ginkgo.By("submit and build block to aggregate feeds", func() {
parser, err := instances[0].lcli.Parser(context.Background())
gomega.Ω(err).Should(gomega.BeNil())
submit, _, _, err := instances[0].cli.GenerateTransaction(
context.Background(),
parser,
nil,
&actions.UploadEntity{
EntityIndex: 0,
EntityType: 0,
Payload: []byte(`{ "ticker": "AMD", "price": 999 }`),
},
factory,
)
gomega.Ω(err).Should(gomega.BeNil())
gomega.Ω(submit(context.Background())).Should(gomega.BeNil())
accept := expectBlk(instances[0])
results := accept()
gomega.Ω(results).Should(gomega.HaveLen(1))
})
ginkgo.By("submit query transaction", func() {
wq := &actions.WarpQuery{
EntityIndex: 0,
DestinationChainID: ids.GenerateTestID(),
}
wtb, err := wq.Marshal()
gomega.Ω(err).Should(gomega.BeNil())
uwm, err := warp.NewUnsignedMessage(networkID, ids.Empty, wtb)
gomega.Ω(err).Should(gomega.BeNil())
wm, err := warp.NewMessage(uwm, &warp.BitSetSignature{})
gomega.Ω(err).Should(gomega.BeNil())
tx := chain.NewTx(
&chain.Base{
ChainID: instances[0].chainID,
Timestamp: hutils.UnixRMilli(-1, 5*consts.MillisecondsPerSecond),
UnitPrice: 1000,
},
wm,
&actions.Query{},
)
msg, err := tx.Digest()
gomega.Ω(err).Should(gomega.BeNil())
auth, err := factory.Sign(msg, tx.Action)
gomega.Ω(err).Should(gomega.BeNil())
tx.Auth = auth
p := codec.NewWriter(0, consts.MaxInt)
gomega.Ω(tx.Marshal(p)).Should(gomega.BeNil())
gomega.Ω(p.Err()).Should(gomega.BeNil())
txID, err := instances[0].cli.SubmitTx(
context.Background(),
p.Bytes(),
)
fmt.Fprintf(ginkgo.GinkgoWriter, "transactionid: %s\n", txID.String())
gomega.Ω(err).Should(gomega.BeNil())
})
// ginkgo.By("build block & check query result", func() {
// accept := expectBlk(instances[0])
// results := accept()
// gomega.Ω(results).Should(gomega.HaveLen(1))
// wm := results[0].WarpMessage
// payload := wm.Payload
// var queryRes actions.QueryResult
// err := json.Unmarshal(payload, &queryRes)
// gomega.Ω(err).Should(gomega.BeNil())
// gomega.Ω(queryRes.EntityType).Should(gomega.Equal(uint64(0)))
// entityPayload := queryRes.Payload
// stk, err := oracle.UnmarshalStock(entityPayload)
// gomega.Ω(err).Should(gomega.BeNil())
// gomega.Ω(stk.Price).Should(gomega.Equal(uint64(1000)))
// gomega.Ω(stk.Ticker).Should(gomega.Equal("AMD"))
// })
})
})
func expectBlk(i instance) func() []*chain.Result {
ctx := context.TODO()
// manually signal ready
i.vm.Builder().ForceNotify()
// manually ack ready sig as in engine
<-i.toEngine
blk, err := i.vm.BuildBlock(ctx)
if err != nil {
panic(err)
}
gomega.Ω(err).To(gomega.BeNil())
gomega.Ω(blk).To(gomega.Not(gomega.BeNil()))
gomega.Ω(blk.Verify(ctx)).To(gomega.BeNil())
gomega.Ω(blk.Status()).To(gomega.Equal(choices.Processing))
err = i.vm.SetPreference(ctx, blk.ID())
gomega.Ω(err).To(gomega.BeNil())
return func() []*chain.Result {
gomega.Ω(blk.Accept(ctx)).To(gomega.BeNil())
gomega.Ω(blk.Status()).To(gomega.Equal(choices.Accepted))
lastAccepted, err := i.vm.LastAccepted(ctx)
gomega.Ω(err).To(gomega.BeNil())
gomega.Ω(lastAccepted).To(gomega.Equal(blk.ID()))
return blk.(*chain.StatelessBlock).Results()
}
}
var _ common.AppSender = &appSender{}
type appSender struct {
next int
instances []instance
}
func (app *appSender) SendAppGossip(ctx context.Context, appGossipBytes []byte) error {
n := len(app.instances)
sender := app.instances[app.next].nodeID
app.next++
app.next %= n
return app.instances[app.next].vm.AppGossip(ctx, sender, appGossipBytes)
}
func (*appSender) SendAppRequest(context.Context, set.Set[ids.NodeID], uint32, []byte) error {
return nil
}
func (*appSender) SendAppResponse(context.Context, ids.NodeID, uint32, []byte) error {
return nil
}
func (*appSender) SendAppGossipSpecific(context.Context, set.Set[ids.NodeID], []byte) error {
return nil
}
func (*appSender) SendCrossChainAppRequest(context.Context, ids.ID, uint32, []byte) error {
return nil
}
func (*appSender) SendCrossChainAppResponse(context.Context, ids.ID, uint32, []byte) error {
return nil
}