422 lines
12 KiB
Go
422 lines
12 KiB
Go
package tests
|
|
|
|
import (
|
|
"kerma/helpers"
|
|
"kerma/models"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/docker/go/canonical/json"
|
|
. "github.com/smartystreets/goconvey/convey"
|
|
)
|
|
|
|
func TestState(t *testing.T) {
|
|
// Prepare
|
|
testDir := t.TempDir()
|
|
t.Setenv("KERMA_STORE_BASE_DIR", testDir)
|
|
t.Setenv("KERMA_INITIAL_PEER_LIST", "example.com:18018")
|
|
|
|
var validator helpers.Validator
|
|
var config helpers.Config
|
|
|
|
validator.Construct()
|
|
config.Construct()
|
|
|
|
peerList := []models.Peer{
|
|
{
|
|
Name: "example.com:18018",
|
|
Active: false,
|
|
},
|
|
}
|
|
|
|
Convey("Given a new state object", t, func() {
|
|
var state models.State
|
|
|
|
Convey("When the Construct method is called", func() {
|
|
state.Construct()
|
|
|
|
Convey("A valid object should be created", func() {
|
|
So(state.Validator, ShouldResemble, validator)
|
|
So(state.Config, ShouldResemble, config)
|
|
So(state.PeerList, ShouldResemble, peerList)
|
|
So(state.Chain, ShouldResemble, []models.Block{*models.GetGenesisBlock()})
|
|
So(state.Transactions, ShouldResemble, map[string]*models.Transaction{})
|
|
})
|
|
})
|
|
|
|
state.Construct()
|
|
Convey("When the CompleteHandshake method is called", func() {
|
|
Convey("When the peer is already in the list, it should be marked as active", func() {
|
|
state.CompleteHandshake("example.com:18018")
|
|
So(state.PeerList[0].Active, ShouldEqual, true)
|
|
})
|
|
|
|
Convey("When the peer is not in the list, it should be created and marked as active", func() {
|
|
state.CompleteHandshake("valid.host:18018")
|
|
So(state.PeerList[1].Name, ShouldEqual, "valid.host:18018")
|
|
So(state.PeerList[1].Active, ShouldEqual, true)
|
|
})
|
|
})
|
|
|
|
Convey("When the CheckForHandshake method is called", func() {
|
|
Convey("When there is a finished handshake, 'true' should be returned", func() {
|
|
state.PeerList[0].Active = true
|
|
So(state.CheckForHandshake("example.com:18018"), ShouldEqual, true)
|
|
})
|
|
|
|
Convey("When there is no finished handshake, 'false' should be returned", func() {
|
|
So(state.CheckForHandshake("missing.host:18018"), ShouldEqual, false)
|
|
})
|
|
})
|
|
|
|
Convey("When the FetchPeerListResponse method is called, the list of peers should be returned", func() {
|
|
res := state.FetchPeerListResponse()
|
|
|
|
peerListResponse := models.PeerListResponse{
|
|
Type: "peers",
|
|
Peers: []string{
|
|
"example.com:18018",
|
|
},
|
|
}
|
|
|
|
So(res, ShouldResemble, peerListResponse)
|
|
})
|
|
|
|
Convey("When the FindPeerByName method is called", func() {
|
|
Convey("When the peer exists, it should be returned", func() {
|
|
res := state.FindPeerByName("example.com:18018")
|
|
So(res, ShouldEqual, &state.PeerList[0])
|
|
})
|
|
|
|
Convey("When the peer does not exist, nil should be returned", func() {
|
|
res := state.FindPeerByName("example1.com:18018")
|
|
So(res, ShouldEqual, nil)
|
|
})
|
|
})
|
|
|
|
Convey("When the RemovePeerByName method is called", func() {
|
|
SkipConvey("When the peer exists, it should be removed", func() {
|
|
prevLen := len(state.PeerList)
|
|
state.RemovePeerByName("example.com:18018")
|
|
So(len(state.PeerList), ShouldEqual, prevLen-1)
|
|
})
|
|
|
|
Convey("When the peer does not exist, nothing should be done", func() {
|
|
prevLen := len(state.PeerList)
|
|
state.RemovePeerByName("example1.com:18018")
|
|
So(len(state.PeerList), ShouldEqual, prevLen)
|
|
})
|
|
})
|
|
|
|
Convey("When the ParsePeerListResponse method is called", func() {
|
|
Convey("With a non-empty input, all new peers should be added to the peer list", func() {
|
|
peerListResponse := models.PeerListResponse{
|
|
Type: "peers",
|
|
Peers: []string{
|
|
"sub.example.com:18018",
|
|
"sub1.example.com:18018",
|
|
},
|
|
}
|
|
|
|
state.ParsePeerListResponse(&peerListResponse)
|
|
So(state.FindPeerByName("sub.example.com:18018"), ShouldResemble, &models.Peer{Name: "sub.example.com:18018", Active: false})
|
|
So(state.FindPeerByName("sub1.example.com:18018"), ShouldResemble, &models.Peer{Name: "sub1.example.com:18018", Active: false})
|
|
})
|
|
|
|
Convey("With an empty input, no new peers should be added to the peer list", func() {
|
|
peerListResponse := models.PeerListResponse{
|
|
Type: "peers",
|
|
Peers: []string{},
|
|
}
|
|
|
|
prevLen := len(state.PeerList)
|
|
state.ParsePeerListResponse(&peerListResponse)
|
|
So(len(state.PeerList), ShouldEqual, prevLen)
|
|
})
|
|
})
|
|
|
|
Convey("When the DumpPeerListStore method is called, the peer list should be saved to the peer list store file", func() {
|
|
state.DumpPeerListStore()
|
|
|
|
res, oserr := os.Stat(state.Config.PeerListStore)
|
|
|
|
file, _ := os.ReadFile(state.Config.PeerListStore)
|
|
peerList := []string{}
|
|
err := json.Unmarshal([]byte(file), &peerList)
|
|
|
|
So(oserr, ShouldBeNil)
|
|
So(res.Size(), ShouldNotEqual, 0)
|
|
So(res.IsDir(), ShouldBeFalse)
|
|
So(err, ShouldBeNil)
|
|
So(len(peerList), ShouldEqual, 3)
|
|
So(peerList[0], ShouldEqual, "example.com:18018")
|
|
})
|
|
|
|
Convey("Given a correct block", func() {
|
|
previd := "00000000a420b7cefa2b7730243316921ed59ffe836e111ca3801f82a4f5360e"
|
|
txid := "2a9458a2e75ed8bd0341b3cb2ab21015bbc13f21ea06229340a7b2b75720c4df"
|
|
height := uint64(1)
|
|
transaction := models.Transaction{
|
|
Type: "transaction",
|
|
Height: &height,
|
|
Outputs: []models.Output{
|
|
{
|
|
Pubkey: "f66c7d51551d344b74e071d3b988d2bc09c3ffa82857302620d14f2469cfbf60",
|
|
Value: 50000000000000,
|
|
},
|
|
},
|
|
}
|
|
|
|
state.Transactions[txid] = &transaction
|
|
|
|
block := models.Block{
|
|
Type: "block",
|
|
Txids: []string{
|
|
txid,
|
|
},
|
|
Nonce: "000000000000000000000000000000000000000000000000000000009d8b60ea",
|
|
Previd: &previd,
|
|
Created: 1624220079,
|
|
Target: "00000002af000000000000000000000000000000000000000000000000000000",
|
|
Miner: "Snekel testminer",
|
|
Note: "First block after genesis with CBTX",
|
|
}
|
|
|
|
Convey("When AppendToChain is called", func() {
|
|
err := state.AppendToChain(&block)
|
|
|
|
Convey("No error should be returned", func() {
|
|
So(err, ShouldBeNil)
|
|
})
|
|
|
|
Convey("When GetChainTip is called, the new block should be returned", func() {
|
|
So(state.GetChainTip(), ShouldResemble, &block)
|
|
})
|
|
|
|
Convey("When GetBlock is called, the new block should be returned", func() {
|
|
So(state.GetBlock(block.GetID()), ShouldResemble, &block)
|
|
})
|
|
|
|
Convey("When GetMissingTransactionsInBlock is called, an empty array and no error should be returned", func() {
|
|
res, err := state.GetMissingTransactionsInBlock(&block)
|
|
So(res, ShouldBeEmpty)
|
|
So(err, ShouldBeNil)
|
|
})
|
|
|
|
Convey("When the DumpBlockStore method is called, the blockchain should be saved to the blockchain store file", func() {
|
|
state.DumpBlockStore()
|
|
|
|
res, oserr := os.Stat(state.Config.BlockStore)
|
|
|
|
file, _ := os.ReadFile(state.Config.BlockStore)
|
|
chain := []models.Block{}
|
|
err := json.Unmarshal([]byte(file), &chain)
|
|
|
|
// Set UTXO of both chain blocks and Height, as they are always recalculated
|
|
genesis := models.GetGenesisBlock()
|
|
genesis.UTXOSet = map[string]uint64(nil)
|
|
block.UTXOSet = map[string]uint64(nil)
|
|
block.Height = 0
|
|
|
|
So(oserr, ShouldBeNil)
|
|
So(res.Size(), ShouldNotEqual, 0)
|
|
So(res.IsDir(), ShouldBeFalse)
|
|
So(err, ShouldBeNil)
|
|
So(len(chain), ShouldEqual, 2)
|
|
So(&chain[0], ShouldResemble, genesis)
|
|
So(&chain[1], ShouldResemble, &block)
|
|
})
|
|
})
|
|
})
|
|
|
|
Convey("Given an incorrect block", func() {
|
|
previd := "00000000a420b7cefa2b7730243316921ed59ffe836e111ca3801f82a4f5360e"
|
|
txid := "2a9458a2e75ed8bd0341b3cb2ab21015bbc13f21ea06229340a7b2b75720c4df"
|
|
height := uint64(1)
|
|
transaction := models.Transaction{
|
|
Type: "transaction",
|
|
Height: &height,
|
|
Outputs: []models.Output{
|
|
{
|
|
Pubkey: "f66c7d51551d344b74e071d3b988d2bc09c3ffa82857302620d14f2469cfbf60",
|
|
Value: 50000000000000,
|
|
},
|
|
},
|
|
}
|
|
|
|
state.Transactions[txid] = &transaction
|
|
|
|
block := models.Block{
|
|
Type: "block",
|
|
Txids: []string{
|
|
txid,
|
|
},
|
|
Nonce: "000000000000000000000000000000000000000000000000000000009d8b60e2",
|
|
Previd: &previd,
|
|
Created: 1624220079,
|
|
Target: "00000002af000000000000000000000000000000000000000000000000000000",
|
|
Miner: "Snekel testminer",
|
|
Note: "First block after genesis with CBTX",
|
|
}
|
|
|
|
Convey("When AppendToChain is called", func() {
|
|
err := state.AppendToChain(&block)
|
|
|
|
Convey("An error should be returned", func() {
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
})
|
|
})
|
|
|
|
Convey("Given a valid coinbase transaction", func() {
|
|
height := uint64(1)
|
|
transaction := models.Transaction{
|
|
Type: "transaction",
|
|
Height: &height,
|
|
Outputs: []models.Output{
|
|
{
|
|
Pubkey: "62b7c521cd9211579cf70fd4099315643767b96711febaa5c76dc3daf27c281c",
|
|
Value: 50000000000000,
|
|
},
|
|
},
|
|
}
|
|
|
|
Convey("When AppendTransaction is called", func() {
|
|
err := state.AppendTransaction(&transaction)
|
|
|
|
Convey("No error should be returned", func() {
|
|
So(err, ShouldBeNil)
|
|
})
|
|
|
|
Convey("GetTransaction should return the transaction", func() {
|
|
So(state.GetTransaction(transaction.GetID()), ShouldResemble, &transaction)
|
|
})
|
|
|
|
Convey("GetMempoolTransactionIDs should return an empty array", func() {
|
|
So(state.GetMempoolTransactionIDs(), ShouldBeEmpty)
|
|
})
|
|
})
|
|
})
|
|
|
|
Convey("Given an invalid coinbase transaction", func() {
|
|
height := uint64(1)
|
|
transaction := models.Transaction{
|
|
Type: "transaction",
|
|
Height: &height,
|
|
Outputs: []models.Output{},
|
|
}
|
|
|
|
Convey("When AppendTransaction is called", func() {
|
|
err := state.AppendTransaction(&transaction)
|
|
|
|
Convey("An error should be returned", func() {
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("GetMempoolTransactionIDs should return an empty array", func() {
|
|
So(state.GetMempoolTransactionIDs(), ShouldBeEmpty)
|
|
})
|
|
})
|
|
})
|
|
|
|
Convey("Given a valid regular transaction", func() {
|
|
height := uint64(1)
|
|
coinbase := models.Transaction{
|
|
Type: "transaction",
|
|
Height: &height,
|
|
Confirmed: true,
|
|
Outputs: []models.Output{
|
|
{
|
|
Pubkey: "f66c7d51551d344b74e071d3b988d2bc09c3ffa82857302620d14f2469cfbf60",
|
|
Value: 50000000000000,
|
|
},
|
|
},
|
|
}
|
|
|
|
state.Transactions[coinbase.GetID()] = &coinbase
|
|
|
|
transaction := models.Transaction{
|
|
Type: "transaction",
|
|
Inputs: []models.Input{
|
|
{
|
|
Outpoint: models.Outpoint{
|
|
Index: 0,
|
|
Txid: "2a9458a2e75ed8bd0341b3cb2ab21015bbc13f21ea06229340a7b2b75720c4df",
|
|
},
|
|
Sig: "49cc4f9a1fb9d600a7debc99150e7909274c8c74edd7ca183626dfe49eb4aa21c6ff0e4c5f0dc2a328ad6b8ba10bf7169d5f42993a94bf67e13afa943b749c0b",
|
|
},
|
|
},
|
|
Outputs: []models.Output{
|
|
{
|
|
Pubkey: "c7c2c13afd02be7986dee0f4630df01abdbc950ea379055f1a423a6090f1b2b3",
|
|
Value: 50,
|
|
},
|
|
},
|
|
}
|
|
|
|
Convey("When AppendTransaction is called", func() {
|
|
err := state.AppendTransaction(&transaction)
|
|
|
|
Convey("No error should be returned", func() {
|
|
So(err, ShouldBeNil)
|
|
})
|
|
|
|
Convey("GetTransaction should return the transaction", func() {
|
|
So(state.GetTransaction(transaction.GetID()), ShouldResemble, &transaction)
|
|
})
|
|
|
|
Convey("GetMempoolTransactionIDs should return an array with the transaction", func() {
|
|
So(state.GetMempoolTransactionIDs(), ShouldNotBeEmpty)
|
|
So(len(state.GetMempoolTransactionIDs()), ShouldEqual, 1)
|
|
So(state.GetMempoolTransactionIDs()[0], ShouldEqual, transaction.GetID())
|
|
})
|
|
|
|
Convey("When the DumpTransactionStore method is called, the confirmed transactions should be saved to the store file", func() {
|
|
state.DumpTransactionStore()
|
|
|
|
res, oserr := os.Stat(state.Config.TransactionStore)
|
|
|
|
file, _ := os.ReadFile(state.Config.TransactionStore)
|
|
store := map[string]*models.Transaction{}
|
|
err := json.Unmarshal([]byte(file), &store)
|
|
|
|
// Set confirmed to false, as this is set in the parse function
|
|
coinbase.Confirmed = false
|
|
|
|
So(oserr, ShouldBeNil)
|
|
So(res.Size(), ShouldNotEqual, 0)
|
|
So(res.IsDir(), ShouldBeFalse)
|
|
So(err, ShouldBeNil)
|
|
So(len(store), ShouldEqual, 1)
|
|
So(store[coinbase.GetID()], ShouldResemble, &coinbase)
|
|
})
|
|
})
|
|
})
|
|
|
|
Convey("Given an invalid regular transaction", func() {
|
|
transaction := models.Transaction{
|
|
Type: "transaction",
|
|
Inputs: []models.Input{},
|
|
Outputs: []models.Output{
|
|
{
|
|
Pubkey: "c7c2c13afd02be7986dee0f4630df01abdbc950ea379055f1a423a6090f1b2b3",
|
|
Value: 50,
|
|
},
|
|
},
|
|
}
|
|
|
|
Convey("When AppendTransaction is called", func() {
|
|
err := state.AppendTransaction(&transaction)
|
|
|
|
Convey("An error should be returned", func() {
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("GetMempoolTransactionIDs should return an empty array", func() {
|
|
So(state.GetMempoolTransactionIDs(), ShouldBeEmpty)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
}
|